InstantRunoff   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 147
rs 10
c 0
b 0
f 0
ccs 73
cts 73
cp 1
wmc 30
lcom 1
cbo 4

4 Methods

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