Completed
Branch dev-2.0 (18c912)
by Boudry
03:14
created

InstantRunoff::compute()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
nc 10
nop 0
dl 0
loc 54
ccs 32
cts 32
cp 1
crap 8
rs 7.7591
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
    Part of INSTANT-RUNOFF method Module - From the original Condorcet PHP
4
5
    Condorcet PHP - Election manager and results calculator.
6
    Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
7
8
    By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
9
    https://github.com/julien-boudry/Condorcet
10
*/
11
declare(strict_types=1);
12
13
namespace CondorcetPHP\Condorcet\Algo\Methods;
14
15
use CondorcetPHP\Condorcet\Algo\Method;
16
use CondorcetPHP\Condorcet\Algo\MethodInterface;
17
use CondorcetPHP\Condorcet\Algo\Tools\PairwiseStats;
18
use CondorcetPHP\Condorcet\Result;
19
20
class InstantRunoff extends Method implements MethodInterface
21
{
22
    // Method Name
23
    public const METHOD_NAME = ['Instant-runoff', 'InstantRunoff', 'preferential voting', 'ranked-choice voting', 'alternative vote', 'AlternativeVote', 'transferable vote', 'Vote alternatif'];
24
25
    public static $starting = 1;
26
27
    protected $_Stats;
28
29 5
    protected function getStats(): array
30
    {
31 5
        $stats = [];
32
33 5
        foreach ($this->_Stats as $oneIterationKey => $oneIterationData) :
34 5
            if (count($oneIterationData) === 1) :
35 4
                break;
36
            endif;
37
38 5
            foreach ($oneIterationData as $candidateKey => $candidateValue) :
39 5
                $stats[$oneIterationKey][(string) $this->_selfElection->getCandidateObjectFromKey($candidateKey)] = $candidateValue;
40
            endforeach;
41
        endforeach;
42
43 5
        return $stats;
44
    }
45
46
47
/////////// COMPUTE ///////////
48
49
    //:: Alternative Vote ALGORITHM. :://
50
51 5
    protected function compute (): void
52
    {
53 5
        $candidateCount = $this->_selfElection->countCandidates();
54 5
        $majority = $this->_selfElection->sumValidVotesWeightWithConstraints() / 2;
55
56 5
        $candidateDone = [];
57 5
        $result = [];
58 5
        $CandidatesWinnerCount = 0;
59 5
        $CandidatesLoserCount = 0;
60
61 5
        $iteration = 0;
62
63 5
        while (count($candidateDone) < $candidateCount) :
64 5
            $score = $this->makeScore($candidateDone);
65 5
            $maxScore = max($score);
66 5
            $minScore = min($score);
67
68 5
            $this->_Stats[++$iteration] = $score;
69
70 5
            if ( $maxScore > $majority ) :
71 4
                foreach ($score as $candidateKey => $candidateScore) :
72 4
                    if ($candidateScore !== $maxScore) :
73 4
                        continue;
74
                    else :
75 4
                        $candidateDone[] = $candidateKey;
76 4
                        $result[++$CandidatesWinnerCount][] = $candidateKey;
77
                    endif;
78
                endforeach;
79
            else :
80 5
                $LosersToRegister = [];
81
82 5
                foreach ($score as $candidateKey => $candidateScore) :
83 5
                    if ($candidateScore !== $minScore) :
84 3
                        continue;
85
                    else :
86 5
                        $LosersToRegister[] = $candidateKey;
87
                    endif;
88
                endforeach;
89
90
                // Tie Breaking
91 5
                $round = count($LosersToRegister);
92 5
                for ($i = 1 ; $i < $round ; $i++) : // A little silly. But ultimately shorter and simpler.
93 3
                    $LosersToRegister = $this->tieBreaking($LosersToRegister);
94
                endfor;
95
96 5
                $CandidatesLoserCount += count($LosersToRegister);
97 5
                $candidateDone = array_merge($candidateDone, $LosersToRegister);
98 5
                $result[$candidateCount - $CandidatesLoserCount + 1] = $LosersToRegister;
99
            endif;
100
101
        endwhile;
102
103 5
        $this->_Result = $this->createResult($result);
104 5
    }
105
106 5
    protected function makeScore (array $candidateDone): array
107
    {
108 5
        $score = [];
109 5
        foreach ($this->_selfElection->getCandidatesList() as $oneCandidate) :
110 5
            if (!in_array($this->_selfElection->getCandidateKey($oneCandidate), $candidateDone, true)) :
111 5
                $score[$this->_selfElection->getCandidateKey($oneCandidate)] = 0;
112
            endif;
113
        endforeach;
114
115 5
        foreach ($this->_selfElection->getVotesManager()->getVotesValidUnderConstraintGenerator() as $oneVote) :
116
117 5
            $weight = ($this->_selfElection->isVoteWeightIsAllowed()) ? $oneVote->getWeight() : 1;
118
119 5
            for ($i = 0; $i < $weight; $i++) :
120 5
                $oneRanking = $oneVote->getContextualRanking($this->_selfElection);
121
122 5
                foreach ($oneRanking as $oneRank) :
123 5
                    foreach ($oneRank as $oneCandidate) :
124 5
                        if (count($oneRank) !== 1) :
125 1
                            break;
126 4
                        elseif (!in_array($this->_selfElection->getCandidateKey($oneCandidate), $candidateDone, true)) :
127 4
                            $score[$this->_selfElection->getCandidateKey(reset($oneRank))] += 1;
128 5
                            break 2;
129
                        endif;
130
                    endforeach;
131
                endforeach;
132
            endfor;
133
        endforeach;
134
135 5
        return $score;
136
    }
137
138 3
    protected function tieBreaking (array $candidatesKeys): array
139
    {
140 3
        $pairwise = $this->_selfElection->getPairwise(false);
141 3
        $pairwiseStats = PairwiseStats::PairwiseComparison($pairwise);
142 3
        $tooKeep = [];
143
144 3
        foreach ($candidatesKeys as $oneCandidateKeyTotest) :
145 3
            $select = true;
146 3
            foreach ($candidatesKeys as $oneChallengerKey) :
147 3
                if ($oneCandidateKeyTotest === $oneChallengerKey) :
148 3
                    continue;
149
                endif;
150
151 3
                if (    $pairwise[$oneCandidateKeyTotest]['win'][$oneChallengerKey] > $pairwise[$oneCandidateKeyTotest]['lose'][$oneChallengerKey] ||
152 3
                        $pairwiseStats[$oneCandidateKeyTotest]['balance'] > $pairwiseStats[$oneChallengerKey]['balance'] ||
153 3
                        $pairwiseStats[$oneCandidateKeyTotest]['win'] > $pairwiseStats[$oneChallengerKey]['win']
154
                ) :
155 3
                    $select = false;
156
                endif;
157
            endforeach;
158
159 3
            if ($select) :
160 3
                $tooKeep[] = $oneCandidateKeyTotest;
161
            endif;
162
        endforeach;
163
164 3
        return $tooKeep;
165
    }
166
}
167