File Coverage

blib/lib/Vote/Count/Method/CondorcetDropping.pm
Criterion Covered Total %
statement 117 128 91.4
branch 29 38 76.3
condition 2 2 100.0
subroutine 14 15 93.3
pod 0 3 0.0
total 162 186 87.1


line stmt bran cond sub pod time code
1 1     1   243825 use strict;
  1         9  
  1         30  
2 1     1   6 use warnings;
  1         1  
  1         43  
3 1     1   26 use 5.022;
  1         2  
4 1     1   6 use feature qw /postderef signatures/;
  1         2  
  1         120  
5              
6             package Vote::Count::Method::CondorcetDropping;
7              
8 1     1   400 use namespace::autoclean;
  1         8143  
  1         5  
9 1     1   535 use Moose;
  1         397810  
  1         8  
10             extends 'Vote::Count';
11              
12             our $VERSION='0.007';
13              
14             =head1 NAME
15              
16             Vote::Count::Method::CondorcetDropping
17              
18             =head1 VERSION 0.007
19              
20             =cut
21              
22             # ABSTRACT: Methods which use simple dropping rules to resolve a Winnerless Condorcet Matrix.
23              
24             #buildpod
25              
26             =pod
27              
28             =head1 Condorcet Dropping Methods
29              
30             This module implements dropping methodologies for resolving a Condorcet Matrix with no Winner. Dropping Methodologies apply a rule to either all remaining choices or to those with the least wins to select a choice for elimination.
31              
32              
33             =head2 Basic Dropping Methods
34              
35             Common Dropping Methods are: Boorda Count (with all the attendant weighting issues), Approval, Plurality Loser (TopCount), and Greatest Loss. Greatest Loss is not currently available, and will likely be implemented in the SSD module if and when that is ever written.
36              
37              
38             =head1 SYNOPSIS
39              
40             =cut
41              
42             #buildpod
43              
44 1     1   6463 no warnings 'experimental';
  1         3  
  1         43  
45 1     1   4 use List::Util qw( min max );
  1         2  
  1         97  
46             # use YAML::XS;
47              
48 1     1   559 use Vote::Count::Matrix;
  1         2  
  1         36  
49 1     1   7 use Carp;
  1         2  
  1         1329  
50             # use Try::Tiny;
51             # use Text::Table::Tiny 'generate_markdown_table';
52             # use Data::Printer;
53             # use Data::Dumper;
54              
55             has 'Matrix' => (
56             isa => 'Object',
57             is => 'ro',
58             lazy => 1,
59             builder => '_newmatrix',
60             );
61              
62             # DropStyle: whether to apply drop rule against
63             # all choices ('all') or the least winning ('leastwins').
64             has 'DropStyle' => (
65             isa => 'Str',
66             is => 'ro',
67             default => 'leastwins',
68             );
69              
70             has 'DropRule' => (
71             isa => 'Str',
72             is => 'ro',
73             default => 'plurality',
74             );
75              
76 30     30 0 60 sub GetRound ( $self, $active, $roundnum='' ) {
  30         113  
  30         52  
  30         43  
  30         36  
77 30         937 my $rule = lc( $self->DropRule() );
78 30 100       243 if ( $rule =~ m/(plurality|topcount)/ ) {
    100          
    50          
    0          
79 21         88 return $self->TopCount($active);
80             }
81             elsif ( $rule eq 'approval' ) {
82 2         19 my $round = $self->Approval($active);
83 2         13 $self->logv( "Round $roundnum Approval Totals ", $round->RankTable() );
84 2         9 return $round;
85             }
86             elsif ( $rule eq 'boorda' ) {
87 7         39 my $round = $self->Boorda($active);
88 7         32 $self->logv( "Round $roundnum Boorda Count ", $round->RankTable() );
89 7         27 return $round;
90             }
91             elsif ( $rule eq 'greatestloss' ) {
92 0         0 ...;
93             }
94             else {
95 0         0 croak "undefined dropping rule $rule requested";
96             }
97             }
98              
99 14     14 0 22 sub DropChoice ( $self, $round, @jeapardy ) {
  14         22  
  14         16  
  14         31  
  14         25  
100 14         47 my %roundvotes = $round->RawCount()->%*;
101 14         27 my @eliminate = ();
102 14         39 my $lowest = $round->CountVotes();
103 14         32 for my $j (@jeapardy) {
104 47 100       98 $lowest = $roundvotes{$j} if $roundvotes{$j} < $lowest;
105             }
106 14         36 for my $j (@jeapardy) {
107 47 100       88 if ( $roundvotes{$j} == $lowest ) {
108 16         28 push @eliminate, $j;
109             }
110             }
111 14         44 return @eliminate;
112             }
113              
114 0     0   0 sub _newmatrix ($self) {
  0         0  
  0         0  
115 0         0 return Vote::Count::Matrix->new(
116             'BallotSet' => $self->BallotSet() );
117             }
118              
119 9     9   12 sub _logstart( $self, $active ) {
  9         15  
  9         16  
  9         14  
120 9         20 my $dropdescription = 'Elimination Rule is Applied to All Active Choices.';
121 9 100       6073 if( $self->DropStyle eq 'leastwins') {
122 4         13 $dropdescription =
123             'Elimination Rule is Applied to only Choices with the Fewest Wins.';
124             }
125 9         31 my $rule = '';
126 9 100       267 if ( $self->DropRule() =~ m/(plurality|topcount)/ ) {
    100          
    50          
    0          
127 6         13 $rule = "Drop the Choice With the Lowest TopCount.";
128             }
129             elsif ( $self->DropRule() eq 'approval' ) {
130 1         3 $rule = "Drop the Choice With the Lowest Approval.";
131             }
132             elsif ( $self->DropRule() eq 'boorda' ) {
133 2         5 $rule = "Drop the Choice With the Lowest Borda Score.";
134             }
135             elsif ( $self->DropRule() eq 'greatestloss' ) {
136 0         0 $rule = "Drop the Choice With the Greatest Loss.";
137             }
138             else {
139 0         0 croak "undefined dropping rule $rule requested";
140             }
141             $self->logt( 'CONDORCET SEQUENTIAL DROPPING METHOD',
142             'CHOICES:',
143 9         21 join( ', ', (sort keys %{$active}) ) );
  9         115  
144 9         43 $self->logv( "Elimination Rule: $rule", $dropdescription );
145             }
146              
147 9     9 0 757 sub RunCondorcetDropping ( $self, $active = undef ) {
  9         22  
  9         19  
  9         18  
148 9 50       48 unless ( defined $active ) { $active = $self->BallotSet->{'choices'} }
  9         254  
149 9         22 my $roundctr = 0;
150 9         16 my $maxround = scalar( keys %{$active} );
  9         26  
151 9         44 $self->_logstart( $active);
152             DROPLOOP:
153 9         18 until ( 0 ) {
154 30         3521 $roundctr++;
155 30 50       75 die "DROPLOOP infinite stopped at $roundctr" if $roundctr > $maxround;
156 30         108 my $topcount = $self->TopCount( $active );
157 30         105 my $round = $self->GetRound( $active, $roundctr );
158 30         143 $self->logv( '---', "Round $roundctr TopCount", $topcount->RankTable() );
159 30         140 my $majority = $self->EvaluateTopCountMajority( $topcount );
160 30 100       75 if ( defined $majority->{'winner'} ) {
161 6         118 return $majority->{'winner'};
162             }
163 24         618 my $matrix = Vote::Count::Matrix->new(
164             'BallotSet' => $self->BallotSet,
165             'Active' => $active );
166 24         253 $self->logv( '---', "Round $roundctr Pairings", $matrix->MatrixTable() );
167 24   100     137 my $cw = $matrix->CondorcetWinner() || 0 ;
168 24 100       61 if ( $cw ) {
169 2         5 my $wstr = "* Winner $cw *";
170 2         5 my $rpt = length( $wstr) ;
171 2         14 $self->logt( '*'x$rpt, $wstr, '*'x$rpt );
172 2         15 return $cw;
173             }
174 22         65 my $eliminated = $matrix->CondorcetLoser();
175 22 100       62 if( $eliminated->{'eliminations'}) {
176             # tracking active between iterations of matrix.
177 8         184 $active = $matrix->Active();
178             $self->logv( "Eliminated Condorcet Losers:",
179 8         57 join( ', ', $eliminated->{'eliminated'}->@* ));
180             # active changed, restart loop
181 8         56 next DROPLOOP;
182             }
183 14         26 my @jeapardy = ();
184 14 100       425 if( $self->DropStyle eq 'leastwins') {
185 7         21 @jeapardy = $matrix->LeastWins();
186 7         15 } else { @jeapardy = keys %{$active} }
  7         24  
187 14         51 for my $goodbye ( $self->DropChoice( $round, @jeapardy )) {
188 16         30 delete $active->{ $goodbye };
189 16         57 $self->logv( "Elimminating $goodbye");
190             }
191 14         42 my @remaining = keys $active->%* ;
192 14 50       96 if ( @remaining == 0) {
    100          
193 0         0 $self->logt( "All remaining Choices would be eliminated, Tie between @jeapardy");
194 0         0 return 'tie';
195 0         0 $self->{'tied'} = \@jeapardy;
196             } elsif ( @remaining == 1) {
197 1         3 my $winner = $remaining[0];
198 1         16 $self->logt( "Only 1 choice remains.", "** WINNER : $winner **");
199 1         8 return $winner;
200             }
201             };#infinite DROPLOOP
202              
203             }
204              
205              
206             1;