Completed
Branch dev-1.7.x (c7b0b7)
by Boudry
06:23
created

KemenyYoung::doPossibleRanking()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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