use utf8;
use autodie;
use bigrat;
+use List::MoreUtils qw(nsort_by);
# vote is
# { Voter => opaque,
die unless $places eq ($places + 0);
while (<>) {
- m/^(\w+) ([A-Z]+)$/ or die;
+ next if m/^\w+$/;
+ m/^(\w+) ([A-Z]+)$/ or die "$_ ?";
my $prefs = $1;
my $vote = {
Voter => $1,
Weight => 1.0,
- Prefs => (split //, $prefs),
+ Prefs => [ split //, $prefs ],
};
push @allvotes, $vote;
}
}
}
+our @elected; # $elected[] = $candidate
+
+our @unsorted = @allvotes;
+
+our %sorted;
+# $sorted{$firstpref}{Votes} = [ $vote, ... ]
+# $sorted{$firstpref}{Cand} = $firstpref
+# $sorted{$firstpref}{Total} = $totalweight
+our @sorted; # values same as %sorted
+our @surpluses; # values same as %sorted
+
+our @exhausted; # votes
+
+our %continuing; # $continuing{$candidate}=1
+
+our @stagerecord; # $stagerecord[]{$candidate} = $total
+
+foreach my $vote (@allvotes) {
+ $continuing{$_}=1 foreach @{ $vote->{Prefs} };
+}
+
+sub equalpiles ($@) {
+ my ($how, @sorted) = @_;
+ return () unless @sorted;
+ my $s = $sorted[0];
+ my $eqtotal = $s->{Total};
+ my $count = 0;
+ while ($count < @$sorted && $sorted[$count]{Total} == $eqtotal) {
+ printf "%7s %10s\n", $how, $sorted[$count]{Cand};
+ $count++;
+ }
+
+
for (;;) {
$stage++;
- my %sorted;
- # $sorted{firstpref}{Votes} = [ $vote, ... ]
- # $sorted{firstpref}{Total} = [ $vote, ... ]
- for my $vote (@allvotes) {
- next unless @{ $vote->{Prefs} };
- my $firstpref = $vote->{Prefs}[0];
- push @{ $sorted{$firstpref}{Votes} }, $vote;
+ while (my $vote = shift @unsorted) {
+ my ($firstpref) = grep { $continuing{$_} } @{ $vote->{Prefs} };
+ if (!defined $firstpref) {
+ votelog $vote, "ballot exhausted";
+ push @exhausted, $vote;
+ } else {
+ push @{ $sorted{$firstpref}{Votes} }, $vote;
+ }
}
+ $sorted{$_}{Cand} = $_ foreach keys %sorted;
foreach my $firstpref (sort keys %sorted) {
foreach my $vote (@{ $sorted{$firstpref}{Votes} }) {
- votelog $vote, "counted $vote->{Weight] for $firstpref";
+ votelog $vote, "counted $vote->{Weight} for $firstpref";
$sorted{$firstpref}{Total} += $vote->{Weight};
}
}
- my @sorted = sort {
- $sorted{$b}{Total} <=> $sorted{$a}{Total};
- } keys %sorted;
+ my @sorted;
+ my $sort_update = sub {
+ @sorted = nsort_by { -$_->{Total} } values %sorted;
+ };
+ $sort_update->();
+
+ push @stagerecord, { map { ($_->{Cand}, $_->{Total}) } @sorted };
my $totalvalid = 0;
- foreach my $firstpref (@sorted) {
- $sorted{$firstpref}{Total} += $_->{Weight}
- foreach @{ $sorted{$firstpref}{Votes} };
- printf "%10s %s\n", $firstpref, pr $sorted{$firstpref}{Total};
- $totalvalid += $sorted{$firstpref}{Total};
+ my $countvalid = sub {
+ my ($l, $what) = @_;
+ foreach my $s (@$l) {
+ printf "%-7s %10s %s\n", $what, $s->{Cand}, pr $s->{Total};
+ $totalvalid += $s->{Total};
+ }
+ };
+ $countvalid->(\@sorted, '1stpref');
+ $countvalid->(\@surpluses, 'surplus');
+
+ printf "%7s %10s %s\n", 'TOTAL', '', pr $totalvalid;
+
+ unless ($totalvalid > 0) {
+ printf "No more votes!\n";
+ last;
+ }
+
+ my $placesremain = $places - @elected;
+
+ unless ($placesremain > 0) {
+ printf "Complete.\n";
+ last;
}
- printf "%10s %s\n", 'TOTAL', pr $totalvalid;
- last unless $totalvalid > 0;
+ my $quota = $totalvalid / ($placesremain + 1);
+ printf "%7s %10s %s\n", '', 'quota', pr $quota;
+
+ my $need_to_transfer_surplus = 1;
+
+ # Look for people to elect.
+ # We elect as many as we can, rather than recomputing the (lower) quota
+ # (ERS rules 5.4.9)
+ for (;;) {
+ my $s = $sorted[0];
+ my $topvoters = $s->{Weight};
+ my $surplus = $topvoters - $quota;
+ last unless $surplus > 0;
- my $quota = $totalvalid / ($places + 1);
- printf "\nquota = %10s\n", pr $quota;
+ printf "%7s %10s\n", 'ELECTED', $s->{Cand};
+ push @elected, $s->{Cand};
- my $topfirst = $sorted[0];
- my $topvoters = $sorted{ }{Weight};
- my $surplus = $topvoters - $quota;
- if ($surplus >= 0) {
- printf "%10s ELECTED\n", $sorted[0];
+ my $derate = $topvoters / $surplus;
+ printf "%7s %10s\n", 'derate', $s->{Cand}, pr $derate;
- # transfer the surplus
- my $weightmult = $surplus / $topvoters;
- foreach my $vote (@{ $sorted{$topfirst}{Votes} }) {
- votelog $vote, "part of surplus of weight $weightmult";
- $vote->{Weight} *= $weightmult;
+ foreach my $vote (@{ $s->{Votes} }) {
+ votelog $vote, "elected $s->{Cand}, derated $weightmult";
+ $vote->{Weight} /= $derate;
}
- filterout $topfirst, "candidate was elected";
- } else {
- my $elim = $sorted{ $#sorted };
- printf "%10s eliminated\n", $elim;
- filterout $elim, "candidate was eliminated";
+ push @surpluses, $s;
+ delete %sorted{ $s->{Cand} };
+ delete $continuing{ $s->{Cand} };
+
+ $sort_update->();
+ $need_to_transfer_surplus = 0;
+ # before actually transferring a surplus, we will consider
+ # eliminating, and then reconsider with a lower quota
}
+
+ my $deferredsurplus = sum map { $_->{Total} } @surpluses;
+ printf "%7s %10s %s\n", 'def.srp', 'total', pr $deferredsurplus;
+
+ # Look for people to eliminate
+ # We eliminate before trying to transfer surpluses
+ # ERS 5.2.5
+ for (;;) {
+ last unless @sorted;
+
+ my @elim = equalpiles 'elim?', reverse @sorted;
+ my $elimvote = sum map { $_->{Total} } @elim;
+ my $elimvotetotal = $elimvote * scalar @elim;
+
+ if (@surpluses and $elimvotetotal > $deferredsurplus) {
+ printf "no-elim, un-defer\n";
+ last;
+ }
+
+ if ((scalar keys %continuing) - (scalar @elim) < $placesremain) {
+ # eliminate only one then, and try again
+ printf "elim-tie!\n";
+ @elim = historically_prefer -1, @elim;
+ }
+
+ foreach my $s (@elim) {
+ my $c = $s->{Cand};
+ printf "%7s %10s\n", 'ELIM', $c;
+ my $votes = $s->{Votes};
+ votelog $_, "failed to stop $c elimination" foreach @$votes;
+ delete %continuing{$c};
+ delete %sorted{$c};
+ push @unsorted, @$votes;
+ }
+
+ $sort_update->();
+ $need_to_transfer_surplus = 0;
+ }
+
+ next unless $need_to_transfer_surplus;
+
+ @surpluses = nsort_by { $_->{Total} }, @surpluses;
+ my @surplusxfer = equalpiles 'xfer?', @surpluses;
+ die unless @surplusxfer;
+
+ if (@surplusxfer > 1) {
+ @surplusxfer = historically_prefer +1, @surplusxfer;
+ }
+
+ my $s = $surplusxfer[0];
+ my $c = $s->{Cand};
+ printf "%7s %10s\n", 'xfer', $c;
+ my $votes = $s->{Votes};
+ votelog $_, "surplus transferred" foreach @$votes;
+ @surpluses = grep { $_->{Cand} ne $c } @surpluses;
+ push @unsorted, @$votes;
}
+
+print "done.\n";