Completed
Push — master ( 922505...49fe4c )
by Boudry
05:20
created

KemenyYoung::conflictInfos()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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