Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
83.51% |
81 / 97 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
PairwiseTallier | |
83.51% |
81 / 97 |
|
33.33% |
2 / 6 |
41.82 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
addVote | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
getOptionAbbreviations | |
73.68% |
14 / 19 |
|
0.00% |
0 / 1 |
9.17 | |||
getRowLabels | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
5 | |||
convertMatrixToHtml | |
95.45% |
21 / 22 |
|
0.00% |
0 / 1 |
5 | |||
convertMatrixToText | |
92.86% |
26 / 28 |
|
0.00% |
0 / 1 |
9.03 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll\Talliers; |
4 | |
5 | use Xml; |
6 | |
7 | /** |
8 | * Generic functionality for Condorcet-style pairwise methods. |
9 | * Tested via SchulzeTallier. |
10 | */ |
11 | abstract class PairwiseTallier extends Tallier { |
12 | /** @var array */ |
13 | public $optionIds = []; |
14 | /** @var array */ |
15 | public $victories = []; |
16 | /** @var array|null */ |
17 | public $abbrevs; |
18 | /** @var array */ |
19 | public $rowLabels = []; |
20 | |
21 | public function __construct( $context, $electionTallier, $question ) { |
22 | parent::__construct( $context, $electionTallier, $question ); |
23 | $this->optionIds = []; |
24 | foreach ( $question->getOptions() as $option ) { |
25 | $this->optionIds[] = $option->getId(); |
26 | } |
27 | |
28 | $this->victories = []; |
29 | foreach ( $this->optionIds as $i ) { |
30 | foreach ( $this->optionIds as $j ) { |
31 | $this->victories[$i][$j] = 0; |
32 | } |
33 | } |
34 | } |
35 | |
36 | /** |
37 | * @inheritDoc |
38 | * |
39 | */ |
40 | public function addVote( $ranks ) { |
41 | foreach ( $this->optionIds as $oid1 ) { |
42 | if ( !isset( $ranks[$oid1] ) ) { |
43 | wfDebug( "Invalid vote record, missing option $oid1\n" ); |
44 | |
45 | return false; |
46 | } |
47 | foreach ( $this->optionIds as $oid2 ) { |
48 | # Lower = better |
49 | if ( $ranks[$oid1] < $ranks[$oid2] ) { |
50 | $this->victories[$oid1][$oid2]++; |
51 | } |
52 | } |
53 | } |
54 | |
55 | return true; |
56 | } |
57 | |
58 | public function getOptionAbbreviations() { |
59 | if ( $this->abbrevs === null ) { |
60 | $abbrevs = []; |
61 | foreach ( $this->question->getOptions() as $option ) { |
62 | $text = $option->getMessage( 'text' ); |
63 | $parts = explode( ' ', $text ); |
64 | $initials = ''; |
65 | foreach ( $parts as $part ) { |
66 | $firstLetter = mb_substr( $part, 0, 1 ); |
67 | if ( $part === '' || ctype_punct( $firstLetter ) ) { |
68 | continue; |
69 | } |
70 | $initials .= $firstLetter; |
71 | } |
72 | if ( isset( $abbrevs[$initials] ) ) { |
73 | $index = 2; |
74 | while ( isset( $abbrevs[$initials . $index] ) ) { |
75 | $index++; |
76 | } |
77 | $initials .= $index; |
78 | } |
79 | $abbrevs[$initials] = $option->getId(); |
80 | } |
81 | $this->abbrevs = array_flip( $abbrevs ); |
82 | } |
83 | |
84 | return $this->abbrevs; |
85 | } |
86 | |
87 | public function getRowLabels( $format = 'html' ) { |
88 | if ( !isset( $this->rowLabels[$format] ) ) { |
89 | $rowLabels = []; |
90 | $abbrevs = $this->getOptionAbbreviations(); |
91 | foreach ( $this->question->getOptions() as $option ) { |
92 | if ( $format == 'html' ) { |
93 | $label = $option->parseMessage( 'text' ); |
94 | } else { |
95 | $label = $option->getMessage( 'text' ); |
96 | } |
97 | if ( $label !== $abbrevs[$option->getId()] ) { |
98 | $label .= ' (' . $abbrevs[$option->getId()] . ')'; |
99 | } |
100 | $rowLabels[$option->getId()] = $label; |
101 | } |
102 | $this->rowLabels[$format] = $rowLabels; |
103 | } |
104 | |
105 | return $this->rowLabels[$format]; |
106 | } |
107 | |
108 | public function convertMatrixToHtml( $matrix, $rankedIds ) { |
109 | $abbrevs = $this->getOptionAbbreviations(); |
110 | $rowLabels = $this->getRowLabels( 'html' ); |
111 | |
112 | $s = "<table class=\"securepoll-results\">"; |
113 | |
114 | # Corner box |
115 | $s .= "<tr>\n<th> </th>\n"; |
116 | |
117 | # Header row |
118 | foreach ( $rankedIds as $oid ) { |
119 | $s .= Xml::tags( 'th', [], $abbrevs[$oid] ) . "\n"; |
120 | } |
121 | $s .= "</tr>\n"; |
122 | |
123 | foreach ( $rankedIds as $oid1 ) { |
124 | # Header column |
125 | $s .= "<tr>\n"; |
126 | $s .= Xml::tags( |
127 | 'td', |
128 | [ 'class' => 'securepoll-results-row-heading' ], |
129 | $rowLabels[$oid1] |
130 | ); |
131 | # Rest of the matrix |
132 | foreach ( $rankedIds as $oid2 ) { |
133 | $value = $matrix[$oid1][$oid2] ?? ''; |
134 | if ( is_array( $value ) ) { |
135 | $value = '(' . implode( ', ', $value ) . ')'; |
136 | } |
137 | $s .= Xml::element( 'td', [], $value ) . "\n"; |
138 | } |
139 | $s .= "</tr>\n"; |
140 | } |
141 | $s .= "</table>"; |
142 | |
143 | return $s; |
144 | } |
145 | |
146 | public function convertMatrixToText( $matrix, $rankedIds ) { |
147 | $abbrevs = $this->getOptionAbbreviations(); |
148 | $minWidth = 15; |
149 | $rowLabels = $this->getRowLabels( 'text' ); |
150 | |
151 | # Calculate column widths |
152 | $colWidths = []; |
153 | foreach ( $abbrevs as $id => $abbrev ) { |
154 | if ( strlen( $abbrev ) < $minWidth ) { |
155 | $colWidths[$id] = $minWidth; |
156 | } else { |
157 | $colWidths[$id] = strlen( $abbrev ); |
158 | } |
159 | } |
160 | $headerColumnWidth = $minWidth; |
161 | foreach ( $rowLabels as $label ) { |
162 | $headerColumnWidth = max( $headerColumnWidth, strlen( $label ) ); |
163 | } |
164 | |
165 | # Corner box |
166 | $s = str_repeat( ' ', $headerColumnWidth ) . ' | '; |
167 | |
168 | # Header row |
169 | foreach ( $rankedIds as $oid ) { |
170 | $s .= str_pad( $abbrevs[$oid], $colWidths[$oid] ) . ' | '; |
171 | } |
172 | $s .= "\n"; |
173 | |
174 | # Divider |
175 | $s .= str_repeat( '-', $headerColumnWidth ) . '-+-'; |
176 | foreach ( $rankedIds as $oid ) { |
177 | $s .= str_repeat( '-', $colWidths[$oid] ) . '-+-'; |
178 | } |
179 | $s .= "\n"; |
180 | |
181 | foreach ( $rankedIds as $oid1 ) { |
182 | # Header column |
183 | $s .= str_pad( $rowLabels[$oid1], $headerColumnWidth ) . ' | '; |
184 | |
185 | # Rest of the matrix |
186 | foreach ( $rankedIds as $oid2 ) { |
187 | $value = $matrix[$oid1][$oid2] ?? ''; |
188 | if ( is_array( $value ) ) { |
189 | $value = '(' . implode( ', ', $value ) . ')'; |
190 | } |
191 | $s .= str_pad( $value, $colWidths[$oid2] ) . ' | '; |
192 | } |
193 | $s .= "\n"; |
194 | } |
195 | |
196 | return $s; |
197 | } |
198 | } |