Completed
Push — master ( dcbc34...facd47 )
by Boudry
02:36
created

KemenyYoung   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 96.72%

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 4
dl 0
loc 154
ccs 59
cts 61
cp 0.9672
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
B calcPossibleRanking() 0 22 5
A doPossibleRanking() 0 10 2
B calcRankingScore() 0 21 5
A makeRanking() 0 4 1
A getResult() 0 13 2
A getStats() 0 21 3
A conflictInfos() 0 15 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 = [];
76 5
        $stats['bestScore'] = max($this->_RankingScore);
77 5
        $stats['rankingScore'] = $explicit;
78
79 5
        return $stats;
80
    }
81
82 5
        protected function conflictInfos () : void
83
        {
84 5
            $max = max($this->_RankingScore);
85
86 5
            $conflict = -1;
87 5
            foreach ($this->_RankingScore as $value) :
88 5
                if ($value === $max) :
89 5
                    $conflict++;
90
                endif;
91
            endforeach;
92
93 5
            if ($conflict > 0)  :
94 1
                $this->_Result->addWarning(self::CONFLICT_WARNING_CODE, ($conflict + 1).';'.max($this->_RankingScore) );
95
            endif;
96 5
        }
97
98
99
/////////// COMPUTE ///////////
100
101
102
    //:: Kemeny-Young ALGORITHM. :://
103
104 5
    protected function calcPossibleRanking () : void
105
    {
106 5
        $path = __DIR__ . '/KemenyYoung-Data/'.$this->_selfElection->countCandidates().'.data';
107
108
        // But ... where are the data ?! Okay, old way now...
109 5
        if (!self::$useCache || !file_exists($path)) :
110 1
            $compute = $this->doPossibleRanking( null );
111
        else :
112 4
            $compute = file_get_contents($path);
113
        endif;
114
115 5
        $i = 0;
116 5
        $search = [];
117 5
        $replace = [];
118
119 5
        foreach ($this->_selfElection->getCandidatesList() as $candidate_id => $candidate_name) :
120 5
            $search[] = 's:'.(($i < 10) ? "2" : "3").':"C'.$i++.'"';
121 5
            $replace[] = 'i:'.$candidate_id;
122
        endforeach;
123
124 5
        $this->_PossibleRanking = unserialize( str_replace($search, $replace, $compute) );
125 5
    }
126
127 1
    protected function doPossibleRanking (?string $path = null)
128
    {
129 1
        $permutation = new Permutation ($this->_selfElection->countCandidates());
130
131 1
        if ($path === null) :
132 1
            return $permutation->getResults(true);
133
        else :
134
            $permutation->writeResults($path);
135
        endif;
136
    }
137
138 5
    protected function calcRankingScore () : void
139
    {
140 5
        $this->_RankingScore = [];
141 5
        $pairwise = $this->_selfElection->getPairwise(false);
142
143 5
        foreach ($this->_PossibleRanking as $keyScore => $ranking) :
144 5
            $this->_RankingScore[$keyScore] = 0;
145
146 5
            $do = [];
147
148 5
            foreach ($ranking as $candidateId) :
149 5
                $do[] = $candidateId;
150
151 5
                foreach ($ranking as $rank => $rankCandidate) :
152 5
                    if (!in_array($rankCandidate, $do, true)) :
153 5
                        $this->_RankingScore[$keyScore] += $pairwise[$candidateId]['win'][$rankCandidate];
154
                    endif;
155
                endforeach;
156
            endforeach;
157
        endforeach;
158 5
    }
159
160
161
    /*
162
    I do not know how in the very unlikely event that several possible classifications have the same highest score.
163
    In the current state, one of them is chosen arbitrarily.
164
165
    See issue on Github : https://github.com/julien-boudry/Condorcet/issues/6
166
    */
167 5
    protected function makeRanking () : void
168
    {
169 5
        $this->_Result = $this->createResult($this->_PossibleRanking[ array_search(max($this->_RankingScore), $this->_RankingScore, true) ]);
170 5
    }
171
172
}
173