File Coverage

blib/lib/Vote/Count/Boorda.pm
Criterion Covered Total %
statement 77 78 98.7
branch 11 12 91.6
condition n/a
subroutine 13 13 100.0
pod 0 1 0.0
total 101 104 97.1


line stmt bran cond sub pod time code
1 9     9   4715 use strict;
  9         23  
  9         253  
2 9     9   40 use warnings;
  9         20  
  9         236  
3 9     9   170 use 5.022;
  9         27  
4 9     9   46 use feature qw /postderef signatures/;
  9         21  
  9         923  
5              
6             package Vote::Count::Boorda;
7              
8 9     9   55 use Moose::Role;
  9         23  
  9         79  
9              
10             our $VERSION='0.007';
11 9     9   44823 no warnings 'experimental';
  9         21  
  9         450  
12 9     9   53 use List::Util qw( min max );
  9         17  
  9         622  
13 9     9   52 use Vote::Count::RankCount;
  9         22  
  9         7021  
14             # use Try::Tiny;
15             # use boolean;
16             # use Data::Printer;
17              
18             has 'bordaweight' => (
19             is => 'rw',
20             isa => 'CodeRef',
21             builder => '_buildbordaweight',
22             lazy => 1,
23             );
24              
25             has 'bordadepth' => (
26             is => 'rw',
27             isa => 'Int',
28             default => 0,
29             );
30              
31             =pod
32              
33             =head1 Boorda Wieght
34              
35             Boorda's original method assigned each position the
36             inverse if its position, ie in a 9 choice ballot
37             position 1 was worth 9, while position 9 was worth 1,
38             and position 8 was worth 2.
39              
40             When Creating a VoteCount object the Boorda weight
41             may be set by passing a coderef. The coderef takes
42             two arguments. The first argument is the
43             position of the choice in question.
44             The second argument is the depth of the ballot. The
45             optional bordadepth attribute will set an arbitrary
46             depth. Some popular options such inversion ( where
47             choice $c becomes $c/1 then inverted to 1/$c) don't
48             need to know the depth. In such cases the coderef
49             should just ignore the second argument.
50              
51             The default Weight when none are provided is Boorda's
52             original weight. If the boordadepth attribute is set
53             it will be followed.
54              
55             =cut
56              
57             sub _buildbordaweight {
58             return sub {
59 95     95   131 my ( $x, $y ) = @_ ;
60 95         176 return ( $y +1 - $x) }
61 4     4   102 }
62              
63             =pod
64              
65             =head3 Private Method _boordashrinkballot( $BallotSet, $active )
66              
67             Takes a BallotSet and active list and returns a
68             BallotSet reduced to only the active choices. When
69             choices are removed later choices are promoted.
70              
71             =cut
72              
73 10     10   16 sub _boordashrinkballot ( $BallotSet, $active ) {
  10         16  
  10         17  
  10         15  
74 10         21 my $newballots = {};
75 10         45 my %ballots = $BallotSet->{'ballots'}->%* ;
76 10         35 for my $b ( keys %ballots ) {
77 64         73 my @newballot = ();
78 64         120 for my $item ( $ballots{$b}{'votes'}->@* ) {
79 168 100       261 if ( defined $active->{ $item }) {
80 106         143 push @newballot, $item ;
81             }
82             }
83 64 100       96 if (scalar( @newballot )) {
84 58         89 $newballots->{$b}{'votes'} = \@newballot;
85             $newballots->{$b}{'count'} =
86 58         112 $ballots{$b}->{'count'};
87             }
88             }
89 10         48 return $newballots;
90             }
91              
92 12     12   27 sub _doboordacount( $self, $BoordaTable, $active) {
  12         20  
  12         16  
  12         18  
  12         14  
93 12         23 my $BoordaCount = {};
94 12         312 my $weight = $self->bordaweight;
95             my $depth = $self->bordadepth
96             ? $self->bordadepth
97 12 100       262 : scalar( keys %{$active} );
  11         23  
98 12         41 for my $c ( keys $BoordaTable->%*) {
99 61         135 for my $rank ( keys $BoordaTable->{$c}->%* ) {
100 108 100       213 $BoordaCount->{ $c } = 0 unless defined $BoordaCount->{ $c };
101             $BoordaCount->{ $c } +=
102 108         157 $BoordaTable->{$c}{$rank} *
103             $weight->( $rank, $depth ) ;
104             }
105             }
106 12         32 return $BoordaCount;
107             }
108              
109 11     11 0 1806 sub Boorda ( $self, $active = undef ) {
  11         21  
  11         21  
  11         22  
110 11         279 my %BallotSet = $self->BallotSet()->%*;
111 11         25 my %ballots = ();
112 11 100       29 if ( defined $active ) {
113 9         18 %ballots = %{_boordashrinkballot( \%BallotSet, $active )};
  9         28  
114             }
115             else {
116 2         15 %ballots = $BallotSet{'ballots'}->%*;
117 2         6 $active = $BallotSet{'choices'};
118             }
119 11         46 my %BoordaTable = ( map { $_ => {} } keys( $active->%* ) );
  58         103  
120 11         53 for my $b ( keys %ballots ) {
121 69         124 my @votes = $ballots{$b}->{'votes'}->@*;
122 69         89 my $bcount = $ballots{$b}->{'count'};
123 69         119 for ( my $i = 0 ; $i < scalar(@votes) ; $i++ ) {
124 136         153 my $c = $votes[$i];
125 136 50       185 if ( defined $BoordaTable{$c} ) {
126 136         299 $BoordaTable{$c}->{ $i + 1 } += $bcount;
127             }
128             else {
129 0         0 $BoordaTable{$c}->{ $i + 1 } = $bcount;
130             }
131             }
132             }
133 11         34 my $BoordaCounted =
134             _doboordacount(
135             $self,
136             \%BoordaTable,
137             $active );
138 11         60 return Vote::Count::RankCount->Rank( $BoordaCounted );
139             }
140              
141              
142             # =pod
143              
144             # =head3 RangeBoorda
145              
146             # When applying Boorda to ranged voting the choices are to convert to rcv or to boorda
147             # count multiple choices at the same range the same. This method implements that latter.
148              
149             # =cut
150              
151             # sub RangeBoorda {
152             # ...
153             # }
154              
155              
156             1;