Passed
Branch dev-1.5.x (fb93e4)
by Boudry
04:14
created

KemenyYoung::conflictInfos()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 7
cts 8
cp 0.875
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 6
nop 0
crap 4.0312
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\Election;
17
use Condorcet\Result;
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
34
    // Kemeny Young
35
    protected $_PossibleRanking;
36
    protected $_RankingScore;
37
38
39
40
/////////// PUBLIC ///////////
41
42
43
    // Get the Kemeny ranking
44 2
    public function getResult () : Result
45
    {
46
        // Cache
47 2
        if ( $this->_Result === null ) :
48 2
            $this->calcPossibleRanking();
49 2
            $this->calcRankingScore();
50 2
            $this->makeRanking();
51 2
            $this->conflictInfos();
52
        endif;
53
54
        // Return
55 2
        return $this->_Result;
56
    }
57
58
59 2
    protected function getStats () : array
60
    {
61 2
        $explicit = [];
62
63 2
        foreach ($this->_PossibleRanking as $key => $value) :
64 2
            $explicit[$key] = $value;
65
66
            // Human readable
67 2
            foreach ($explicit[$key] as &$candidate_key) :
68 2
                $candidate_key = $this->_selfElection->getCandidateId($candidate_key);
69
            endforeach;
70
71 2
            $explicit[$key]['score'] = $this->_RankingScore[$key];
72
        endforeach;
73
74 2
        $stats['bestScore'] = max($this->_RankingScore);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$stats was never initialized. Although not strictly required by PHP, it is generally a good practice to add $stats = array(); before regardless.
Loading history...
75 2
        $stats['rankingScore'] = $explicit;
76
77 2
        return $stats;
78
    }
79
80 2
        protected function conflictInfos () : void
81
        {
82 2
            $max = max($this->_RankingScore);
83
84 2
            $conflict = -1;
85 2
            foreach ($this->_RankingScore as $value) :
86 2
                if ($value === $max) :
87 2
                    $conflict++;
88
                endif;
89
            endforeach;
90
91 2
            if ($conflict > 0)  :
92
                $this->_Result->addWarning(self::CONFLICT_WARNING_CODE, ($conflict + 1).';'.max($this->_RankingScore) );
93
            endif;
94 2
        }
95
96
97
/////////// COMPUTE ///////////
98
99
100
    //:: Kemeny-Young ALGORITHM. :://
101
102 2
    protected function calcPossibleRanking () : void
103
    {
104 2
        $path = __DIR__ . '/KemenyYoung-Data/'.$this->_selfElection->countCandidates().'.data';
105
106
        // But ... where are the data ?! Okay, old way now...
107 2
        if (!file_exists($path)) :
108
            $compute = $this->doPossibleRanking( (Condorcet::ENV === 'DEV') ? $path : null );
0 ignored issues
show
Bug introduced by
The constant Condorcet\Condorcet::ENV was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
109
        else :
110 2
            $compute = file_get_contents($path);
111
        endif;
112
113 2
        $i = 0;
114 2
        $search = [];
115 2
        $replace = [];
116
117 2
        foreach ($this->_selfElection->getCandidatesList() as $candidate_id => $candidate_name) :
118 2
            $search[] = 's:'.(($i < 10) ? "2" : "3").':"C'.$i++.'"';
119 2
            $replace[] = 'i:'.$candidate_id;
120
        endforeach;
121
122 2
        $this->_PossibleRanking = unserialize( str_replace($search, $replace, $compute) );
123 2
    }
124
125
    protected function doPossibleRanking (string $path = null)
126
    {
127
        $permutation = new Permutation ($this->_selfElection->countCandidates());
128
129
        if ($path === null) :
130
            return $permutation->getResults(true);
131
        else :
132
            $permutation->writeResults($path);
133
        endif;
134
    }
135
136 2
    protected function calcRankingScore () : void
137
    {
138 2
        $this->_RankingScore = [];
139 2
        $pairwise = $this->_selfElection->getPairwise(false);
140
141 2
        foreach ($this->_PossibleRanking as $keyScore => $ranking) :
142 2
            $this->_RankingScore[$keyScore] = 0;
143
144 2
            $do = [];
145
146 2
            foreach ($ranking as $candidateId) :
147 2
                $do[] = $candidateId;
148
149 2
                foreach ($ranking as $rank => $rankCandidate) :
150 2
                    if (!in_array($rankCandidate, $do, true)) :
151 2
                        $this->_RankingScore[$keyScore] += $pairwise[$candidateId]['win'][$rankCandidate];
152
                    endif;
153
                endforeach;
154
            endforeach;
155
        endforeach;
156 2
    }
157
158
159
    /*
160
    I do not know how in the very unlikely event that several possible classifications have the same highest score.
161
    In the current state, one of them is chosen arbitrarily.
162
163
    See issue on Github : https://github.com/julien-boudry/Condorcet/issues/6
164
    */
165 2
    protected function makeRanking () : void
166
    {
167 2
        $this->_Result = $this->createResult($this->_PossibleRanking[ array_search(max($this->_RankingScore), $this->_RankingScore, true) ]);
168 2
    }
169
170
}
171