KemenyYoung::calcRankingScore()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 0
dl 0
loc 21
rs 9.2728
c 0
b 0
f 0
ccs 12
cts 12
cp 1
crap 5
1
<?php
2
/*
3
    Part of KEMENY–YOUNG 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\KemenyYoung;
14
15
use CondorcetPHP\Condorcet\Result;
16
use CondorcetPHP\Condorcet\Algo\{Method, MethodInterface};
17
use CondorcetPHP\Condorcet\Algo\Tools\Permutation;
18
19
// Kemeny-Young is a Condorcet Algorithm | http://en.wikipedia.org/wiki/Kemeny%E2%80%93Young_method
20
class KemenyYoung extends Method implements MethodInterface
21
{
22
    // Method Name
23
    public const METHOD_NAME = ['Kemeny–Young','Kemeny-Young','Kemeny Young','KemenyYoung','Kemeny rule','VoteFair popularity ranking','Maximum Likelihood Method','Median Relation'];
24
25
    // Method Name
26
    public const CONFLICT_WARNING_CODE = 42;
27
28
    // Limits
29
        /* If you need to put it on 9, You must use ini_set('memory_limit','1024M'); before. The first use will be slower because Kemeny-Young will work without pre-calculated data of Permutations.
30
        Do not try to go to 10, it is not viable! */
31
        public static $MaxCandidates = 8;
32
33
    // Cache
34
    public static $useCache = true;
35
    public static $devWriteCache = false;
36
37
    // Kemeny Young
38
    protected $_PossibleRanking;
39
    protected $_RankingScore;
40
41
42
43
/////////// PUBLIC ///////////
44
45
46
    // Get the Kemeny ranking
47 6
    public function getResult () : Result
48
    {
49
        // Cache
50 6
        if ( $this->_Result === null ) :
51 6
            $this->calcPossibleRanking();
52 6
            $this->calcRankingScore();
53 6
            $this->makeRanking();
54 6
            $this->conflictInfos();
55
        endif;
56
57
        // Return
58 6
        return $this->_Result;
59
    }
60
61
62 6
    protected function getStats () : array
63
    {
64 6
        $explicit = [];
65
66 6
        foreach ($this->_PossibleRanking as $key => $value) :
67 6
            $explicit[$key] = $value;
68
69
            // Human readable
70 6
            foreach ($explicit[$key] as &$candidate_key) :
71 6
                $candidate_key = $this->_selfElection->getCandidateObjectFromKey($candidate_key);
72
            endforeach;
73
74 6
            $explicit[$key]['score'] = $this->_RankingScore[$key];
75
        endforeach;
76
77 6
        $stats = [];
78 6
        $stats['bestScore'] = max($this->_RankingScore);
79 6
        $stats['rankingScore'] = $explicit;
80
81 6
        return $stats;
82
    }
83
84 6
        protected function conflictInfos () : void
85
        {
86 6
            $max = max($this->_RankingScore);
87
88 6
            $conflict = -1;
89 6
            foreach ($this->_RankingScore as $value) :
90 6
                if ($value === $max) :
91 6
                    $conflict++;
92
                endif;
93
            endforeach;
94
95 6
            if ($conflict > 0)  :
96 1
                $this->_Result->addWarning(self::CONFLICT_WARNING_CODE, ($conflict + 1).';'.max($this->_RankingScore) );
97
            endif;
98 6
        }
99
100
101
/////////// COMPUTE ///////////
102
103
104
    //:: Kemeny-Young ALGORITHM. :://
105
106 6
    protected function calcPossibleRanking () : void
107
    {
108 6
        $path = __DIR__ . '/KemenyYoung-Data/'.$this->_selfElection->countCandidates().'.data';
109
110
        // But ... where are the data ?! Okay, old way now...
111 6
        if (!self::$useCache || !file_exists($path)) :
112 2
            $compute = $this->doPossibleRanking( self::$devWriteCache ? $path : null );
113
        else :
114 4
            $compute = file_get_contents($path);
115
        endif;
116
117 6
        $i = 0;
118 6
        $search = [];
119 6
        $replace = [];
120
121 6
        foreach ($this->_selfElection->getCandidatesList() as $candidate_id => $candidate_name) :
122 6
            $search[] = 's:'.(($i < 10) ? "2" : "3").':"C'.$i++.'"';
123 6
            $replace[] = 'i:'.$candidate_id;
124
        endforeach;
125
126 6
        $this->_PossibleRanking = unserialize( str_replace($search, $replace, $compute) );
127 6
    }
128
129 2
    protected function doPossibleRanking (?string $path = null)
130
    {
131 2
        $permutation = new Permutation ($this->_selfElection->countCandidates());
132
133 2
        if ($path === null) :
134 1
            return $permutation->getResults(true);
135
        else :
136 1
            $permutation->writeResults($path);
137 1
            return $permutation->getResults(true);
138
        endif;
139
    }
140
141 6
    protected function calcRankingScore () : void
142
    {
143 6
        $this->_RankingScore = [];
144 6
        $pairwise = $this->_selfElection->getPairwise();
145
146 6
        foreach ($this->_PossibleRanking as $keyScore => $ranking) :
147 6
            $this->_RankingScore[$keyScore] = 0;
148
149 6
            $do = [];
150
151 6
            foreach ($ranking as $candidateId) :
152 6
                $do[] = $candidateId;
153
154 6
                foreach ($ranking as $rank => $rankCandidate) :
155 6
                    if (!in_array($rankCandidate, $do, true)) :
156 6
                        $this->_RankingScore[$keyScore] += $pairwise[$candidateId]['win'][$rankCandidate];
157
                    endif;
158
                endforeach;
159
            endforeach;
160
        endforeach;
161 6
    }
162
163
164
    /*
165
    I do not know how in the very unlikely event that several possible classifications have the same highest score.
166
    In the current state, one of them is chosen arbitrarily.
167
168
    See issue on Github : https://github.com/julien-boudry/Condorcet/issues/6
169
    */
170 6
    protected function makeRanking () : void
171
    {
172 6
        $this->_Result = $this->createResult($this->_PossibleRanking[ array_search(max($this->_RankingScore), $this->_RankingScore, true) ]);
173 6
    }
174
}
175