#!/usr/bin/perl -w
package IRV;
use strict;
use warnings;
my $DEBUG = 0;
sub setDebug($) {
$DEBUG = shift;
}
sub new {
my $class = shift;
return bless [], $class;
}
# voteNameValues( $irnr, [ "a", 3, "B", 5 ] );
# values are ratings, higher is better
sub voteNameValues($$) {
my $counts = shift;
my $vote = shift;
push @{$counts}, $vote;
}
# voteNameValueHash( $self, { "a" => 3, "B" => 5 } );
# values are ratings, higher is better
sub voteNameValueHash($$) {
my $counts = shift;
my $vhi = shift;
my %vh = %{$vhi};
my @nvv = ();
my $name;
my $value;
while ( ($name,$value) = each %vh ) {
push @nvv, $name;
push @nvv, $value;
}
push @{$counts}, [ @nvv ];
}
# voteOrderedNames( $irnr, [ "first choice", "second choice" ] );
sub voteOrderedNames($$) {
my $counts = shift;
my $vote = shift;
my $nv = [];
my $i;
for ( $i = 0; $i <= $#{$vote}; $i++ ) {
# print ${$vote}[$i] . "\t" . ($#{$vote} - $i) . "\n";
push( @{$nv}, ( ${$vote}[$i], $#{$vote} - $i ) );
}
voteNameValues( $counts, $nv );
}
sub bucketize($$$) {
my $counts = shift;
my $buckets = shift;
my $dq = shift;
my $vote;
foreach $vote ( @{$counts} ) {
my $vi = undef;
my $maxr = undef;
my $i;
my $ties = 0;
for ( $i = 0; $i < $#{$vote}; $i += 2 ) {
my $name = $vote->[$i];
if ( $dq->{$name} ) {
# skip
} elsif ( (! defined $vi) || ( $vote->[$i + 1] > $maxr ) ) {
$vi = $i;
$maxr = $vote->[$i + 1];
$ties = 1;
} elsif ( $vote->[$i + 1] == $maxr ) {
$ties++;
}
}
if ( defined $vi ) {
my $name = $vote->[$vi];
if ( $ties > 1 ) {
$name = "TIED_VOTE_LABEL";
}
if ( ! defined $buckets->{$name} ) {
$buckets->{$name} = [];
}
if ( $DEBUG ) {
print "IRV bucketize \"$name\"
\n";
}
push @{$buckets->{$name}}, $vote;
}
}
}
sub get_results($) {
my $counts = shift;
my $vote;
my %dq = ();
my @res = ();
my @sq;
my %buckets = ();
my $active;
my @rebucket = ();
# print( ($#{$counts} + 1) . " votes\n" );
{
# collect names which get no first place votes
my %namesh = ();
foreach $vote ( @{$counts} ) {
my $i;
for ( $i = 0; $i < $#{$vote}; $i += 2 ) {
$namesh{$vote->[$i]} = 1;
}
}
bucketize( $counts, \%buckets, \%dq );
my $name;
foreach $name ( sort keys %namesh ) {
if ( ! $buckets{$name} ) {
$dq{$name} = 1;
unshift( @res, ($name, 0) );
if ( $DEBUG ) {
print "IRV dq \"$name\" = 0
\n";
}
}
}
}
do {
my( $name, $value, $i, $minn, $minv, $ba, $tied );
$minn = undef;
$minv = undef;
$active = 0;
$tied = 0;
while ( $ba = shift @rebucket ) {
bucketize( $ba, \%buckets, \%dq );
}
while ( ($name,$ba) = each %buckets ) {
my $value = $#{$ba} + 1;
if ( $name eq "TIED_VOTE_LABEL" ) {
# skip
} elsif ( (! defined $minv) || ($value < $minv) ) {
$minn = $name;
$minv = $value;
$active++;
$tied = 1;
} elsif ( $value == $minv ) {
$tied++;
$active++;
} else {
$active++;
}
# print "$name\t$value\n";
}
if ( $tied == 1 ) {
unshift( @res, ($minn, $minv) );
$dq{$minn} = 1;
$active--;
if ( $DEBUG ) {
print "IRV dq \"$minn\" = $minv
\n";
}
push @rebucket, $buckets{$minn};
delete $buckets{$minn};
} else {
while ( ($name,$ba) = each %buckets ) {
my $value = $#{$ba} + 1;
if ( $name eq "TIED_VOTE_LABEL" ) {
# skip
} elsif ( $value == $minv ) {
unshift( @res, ($name, $value) );
$dq{$name} = 1;
$active--;
if ( $DEBUG ) {
print "IRV dq \"$name\" = $value
\n";
}
push @rebucket, $buckets{$name};
delete $buckets{$name};
}
}
}
{
my $tb = $buckets{"TIED_VOTE_LABEL"};
if ( defined $tb ) {
push @rebucket, $tb;
delete $buckets{"TIED_VOTE_LABEL"};
if ( $DEBUG ) {
print "IRV rebucketize ties...
\n";
}
}
}
if ( $DEBUG ) {
print "IRV active = $active
\n";
}
} while ( $active > 1 );
my ($name, $ba);
while ( ($name,$ba) = each %buckets ) {
my $value = $#{$ba} + 1;
if ( $name eq "TIED_VOTE_LABEL" ) {
# skip
} else {
unshift( @res, ($name, $value) );
if ( $DEBUG ) {
print "IRV winner \"$name\" = $value
\n";
}
}
}
return @res;
}
sub htmlSummary($) {
my $self = shift;
my $toret = "";
my @r = $self->get_results();
my $i;
$toret .= "
| Name | Best IRV Count |
|---|---|
| " . $r[$i] . " | " . $r[$i+1] . " |