Skip to content

Instantly share code, notes, and snippets.

@RogerDodger
Last active August 29, 2015 14:27
Show Gist options
  • Save RogerDodger/96173c8fac9d8bedda4c to your computer and use it in GitHub Desktop.
Save RogerDodger/96173c8fac9d8bedda4c to your computer and use it in GitHub Desktop.
Ranking algorithms
#!/usr/bin/env perl
use 5.01;
use strict;
use warnings;
use File::Basename qw/dirname/;
use lib dirname($0);
use Rank qw/twipie/;
my @slates;
my $fn = shift // dirname($0) . '/slates.txt';
if (-e $fn) {
open my $fh, '<', $fn;
while (my $line = readline $fh) {
push @slates, [ split /\s+/, $line ];
}
}
else {
while (my $line = <STDIN>) {
push @slates, [ split /\s+/, $line ];
}
}
my ($scores,$error) = twipie(\@slates);
for my $id (reverse sort { $scores->{$a} <=> $scores->{$b} } keys %$scores) {
printf "%-4s %7.3f %8.5f\n", $id, $scores->{$id}, $error->{$id};
}
package Rank;
use 5.01;
use strict;
use warnings;
use base 'Exporter';
our @EXPORT_OK = qw/twipie/;
sub uniq {
my %uniq;
$uniq{$_} = 1 for @_;
keys %uniq;
}
sub flatten {
map { ref $_ eq 'ARRAY' ? map { flatten($_) } @$_ : $_ } @_;
}
# Algorithm: Perfecting Probabilities using Test Scoring (a.k.a. TwiPie)
#
# N = (of times story N has been rated higher than another story) /
# sum over all M {(number of times story N has been compared to story M) / (N+M)}
# Initialising
sub twipie {
my $slates = shift;
# Initialise
my (%scores, %p, %error, %wins, %comps);
# Imaginary team, key guaranteed unique
my $x = join '', uniq flatten @$slates;
for my $n ($x, uniq flatten @$slates) {
$scores{$n} = 1;
$wins{$n} = 0;
$comps{$n} = {};
$p{$n} = [] unless $n eq $x;
for my $m ($x, uniq flatten @$slates) {
$comps{$n}{$m} = 0;
}
}
# Calculate wins and comps
for my $slate (@$slates) {
for my $i (0..$#$slate) {
$wins{$slate->[$i]} += $#$slate - $i;
for my $j ($i+1..$#$slate) {
$comps{$slate->[$i]}{$slate->[$j]} += 1;
$comps{$slate->[$j]}{$slate->[$i]} += 1;
}
}
}
# Add win and loss for each story against imaginary team
for my $n (keys %scores) {
next if $n eq $x;
$wins{$n} += 1;
$wins{$x} += 1;
$comps{$n}{$x} = 2;
$comps{$x}{$n} = 2;
}
# Calculate scores
for (0..100) {
my %newScores;
for my $n (keys %scores) {
my $sum = 0;
for my $m (keys %scores) {
next if $scores{$n} + $scores{$m} == 0;
$sum += $comps{$n}{$m} / ($scores{$n} + $scores{$m});
}
$newScores{$n} = $wins{$n} / $sum;
}
%scores = %newScores;
}
# Calculate probability of match results for each story
#
# Expected outcome for N is N/(N+M)
for my $slate (@$slates) {
for my $i (0..$#$slate) {
for my $j ($i+1..$#$slate) {
my $n = $slate->[$i];
my $m = $slate->[$j];
push @{ $p{$n} }, abs(1 - $scores{$n} / ($scores{$n} + $scores{$m}) );
push @{ $p{$m} }, abs(0 - $scores{$m} / ($scores{$n} + $scores{$m}) );
}
}
}
# Calculate error score, the root mean square error
for my $i (keys %p) {
my $sum = 0;
for my $diff (@{ $p{$i} }) {
$sum += $diff ** 2;
}
$error{$i} = sqrt($sum / @{ $p{$i} });
}
# Remove imaginary team from output
delete $scores{$x};
delete $error{$x};
return (\%scores, \%error);
}
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment