X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=appendix-a6.git;a=blobdiff_plain;f=stv;h=68940f452683825259efcecc5fdc2fffbb7c3a38;hp=ba89fce1bfd4dcd359d86f80fe0555820db25cd8;hb=36928c5e0556b62575c7d1a5849834297a678213;hpb=2c0d1b163477f5472ca5f2495f5f436d5205889f diff --git a/stv b/stv index ba89fce..68940f4 100755 --- a/stv +++ b/stv @@ -20,6 +20,25 @@ use List::MoreUtils qw(nsort_by); our $stage=0; our @allvotes; +our %tiebreak; # $tiebreak{ winningcand }{ losingcand } = 1 + +while (@ARGV && $ARGV[0] =~ m/[<>]/) { + my ($losers, $winners) = ($? eq '<' ? ($`, $') : ($', $`)); + my @losers = sort split /\,/, $losers; + my @winners = sort split /\,/, $winners; + die "@losers @winners" if @losers>1 && @winners>1; + my @all = sort @losers, @winners; + my $record = sub { + my ($how, $howers) = @_; + return unless @$howers == 1; + die "@all $how" if defined $tiebreak{$how}{"@all"}; + $tiebreak{$how}{"@all"} = $howers->[0]; + }; + $record->('winner', \@winners); + $record->('loser', \@losers); + shift @ARGV; +} + our $places = shift @ARGV; die unless $places eq ($places + 0); @@ -95,27 +114,56 @@ sub equalpiles ($@) { sub historically_prefer ($@) { my ($signum, @choices) = @_; + # $signum==+1 means choose candidates with more early preferences return $choices[0] if @choices < 2; my $compare = sub { foreach my $sr (@stagerecord) { - my $d = $sr->{ $a->{Cand} } <=> $sr->{ $b->{Cand} }; - return $d * $signum if $d; + my $d = ($sr->{ $a->{Cand} }//0) <=> ($sr->{ $b->{Cand} }//0); + return -$d * $signum if $d; } return 0; }; - @choices = sort $compare, @choices; + @choices = sort $compare @choices; + + print DEBUG "historically_prefer\n", + Data::Dumper->Dump([\@choices, \@stagerecord], + [qw(_@choices _@stagerecord)]); + + foreach my $s (@choices) { + my $c = $s->{Cand}; + printf " %6s %10s |", 'tiebrk', $c; + foreach my $sr (@stagerecord) { + my $p = pr($sr->{$c} // 0); + $p =~ s/\.0+\b//g; + $p =~ s/ //g; + print "$p|"; + } + print "\n"; + } + $a = $choices[0]; - my $numequal = 0; + my $numequal = 1; for (;;) { - last unless $numequal >= @choices; + last if $numequal >= @choices; $b = $choices[$numequal]; last if $compare->(); + $numequal++; + } + + if ($numequal > 1) { + my $how = $signum > 0 ? 'winner' : 'loser'; + my @all = sort map { $_->{Cand} } @choices[ 0 .. $numequal-1 ]; + my $hower = $tiebreak{$how}{"@all"}; + if (defined $hower) { + printf "%7.7s %10s !!!!!!!!!!\n", "TIE".(uc $how), $hower; + return grep { $_->{Cand} eq $hower } @choices; + } + die "need tie break for $how between @all\n"; } - die 'random choice unimplemented' if $numequal > 1; return $choices[0]; } @@ -160,7 +208,7 @@ for (;;) { } my @sorted; my $things_update = sub { - foreach my $s (@surpluses, values %sorted) { + foreach my $s (values %sorted) { $s->{Total} = sum0 map { $_->{Weight} } @{ $s->{Votes } }; } @sorted = nsort_by { -$_->{Total} } values %sorted; @@ -203,19 +251,31 @@ for (;;) { my $s = $sorted[0]; my $topvoters = $s->{Total}; my $surplus = $topvoters - $quota; - last unless $surplus > 0; + last unless $surplus >= 0; + last unless $placesremain; - printf "%7s %10s ***************\n", 'ELECTED', $s->{Cand}; - push @elected, $s->{Cand}; + my @elect = equalpiles 'elect?', @sorted; - foreach my $vote (@{ $s->{Votes} }) { - votelog $vote, "elected $s->{Cand}"; + if (@elect > $placesremain) { + # oh my god + @elect = historically_prefer +1, @elect; + printf "%7s %10s\n", 'tie!', $elect[0]{Cand}; } - $s->{Surplus} = $surplus; - push @newsurpluses, $s; - delete $sorted{ $s->{Cand} }; - delete $continuing{ $s->{Cand} }; + foreach $s (@elect) { + printf "%7s %10s ***************\n", 'ELECTED', $s->{Cand}; + push @elected, $s->{Cand}; + + foreach my $vote (@{ $s->{Votes} }) { + votelog $vote, "elected $s->{Cand}"; + } + + $s->{Surplus} = $surplus; + push @newsurpluses, $s; + delete $sorted{ $s->{Cand} }; + delete $continuing{ $s->{Cand} }; + $placesremain--; + } $things_update->(); } @@ -234,15 +294,18 @@ for (;;) { grep { !!voteliveprefs $_ } @$votes; + my $derate = 1/1; printf "%7s %10s %s\n", 'xfrable', $s->{Cand}, pr $transferrable; if ($transferrable > $surplus) { - my $derate = $transferrable / $surplus; + $derate = $transferrable / $surplus; printf "%7s %10s %s\n", 'derate', $s->{Cand}, pr $derate; foreach my $vote (@{ $s->{Votes} }) { votelog $vote, "part of surplus, derated ". pr $derate; $vote->{Weight} /= $derate; } } + $s->{Total} = $transferrable / $derate; + push @surpluses, $s; $things_update->();