http://www.mediawiki.org/wiki/Special:Code/MediaWiki/88962

Revision: 88962
Author:   happy-melon
Date:     2011-05-27 09:42:25 +0000 (Fri, 27 May 2011)
Log Message:
-----------
Alternative Vote tallier for SecurePoll.

Modified Paths:
--------------
    trunk/extensions/SecurePoll/SecurePoll.i18n.php
    trunk/extensions/SecurePoll/SecurePoll.php
    trunk/extensions/SecurePoll/includes/talliers/Tallier.php
    trunk/extensions/SecurePoll/resources/SecurePoll.css

Added Paths:
-----------
    trunk/extensions/SecurePoll/includes/talliers/AlternativeVoteTallier.php

Modified: trunk/extensions/SecurePoll/SecurePoll.i18n.php
===================================================================
--- trunk/extensions/SecurePoll/SecurePoll.i18n.php     2011-05-27 09:34:16 UTC 
(rev 88961)
+++ trunk/extensions/SecurePoll/SecurePoll.i18n.php     2011-05-27 09:42:25 UTC 
(rev 88962)
@@ -163,6 +163,9 @@
        'securepoll-strength-matrix' => 'Path strength matrix',
        'securepoll-ranks' => 'Final ranking',
        'securepoll-average-score' => 'Average score',
+       'securepoll-round' => 'Round $1',
+       'securepoll-spoilt' => '(Spoilt)',
+       'securepoll-exhausted' => '(Exhausted)',
 );
 
 /** Message documentation (Message documentation)
@@ -238,6 +241,9 @@
        'securepoll-subpage-list' => 'Link text to a sub page in the SecurePoll 
extension where users can list poll information.',
        'securepoll-subpage-dump' => 'Link text to a sub page in the SecurePoll 
extension where users can dump results.',
        'securepoll-subpage-tally' => 'Link text to a sub page in the 
SecurePoll extension where users can tally.',
+       'securepoll-round' => 'Column header for tables on tallies which take 
place over multiple rounds; parameter is a roman numeral.',
+       'securepoll-spoilt' => 'Row label for counting ballots which were 
spoilt (not correctly filled in or indecipherable',
+       'securepoll-exhausted' => 'Row label for counting ballots which have 
been exhausted in a multi-round counting system',
 );
 
 /** Afrikaans (Afrikaans)

Modified: trunk/extensions/SecurePoll/SecurePoll.php
===================================================================
--- trunk/extensions/SecurePoll/SecurePoll.php  2011-05-27 09:34:16 UTC (rev 
88961)
+++ trunk/extensions/SecurePoll/SecurePoll.php  2011-05-27 09:42:25 UTC (rev 
88962)
@@ -92,6 +92,7 @@
        'SecurePoll_PairwiseTallier' => 
"$dir/includes/talliers/PairwiseTallier.php",
        'SecurePoll_PluralityTallier' => 
"$dir/includes/talliers/PluralityTallier.php",
        'SecurePoll_SchulzeTallier' => 
"$dir/includes/talliers/SchulzeTallier.php",
+       'SecurePoll_AlternativeVoteTallier' => 
"$dir/includes/talliers/AlternativeVoteTallier.php",
        'SecurePoll_Tallier' => "$dir/includes/talliers/Tallier.php",
 
        # user

Added: trunk/extensions/SecurePoll/includes/talliers/AlternativeVoteTallier.php
===================================================================
--- trunk/extensions/SecurePoll/includes/talliers/AlternativeVoteTallier.php    
                        (rev 0)
+++ trunk/extensions/SecurePoll/includes/talliers/AlternativeVoteTallier.php    
2011-05-27 09:42:25 UTC (rev 88962)
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * Tallier for AlternativeVote system.
+ */
+class SecurePoll_AlternativeVoteTallier extends SecurePoll_Tallier {
+
+       /**
+        * Array of ballot bins for each candidate
+        * @var array
+        */
+       var $ballots = array();
+
+       /**
+        * Votes which give the same preference to more than one candidate are 
considered
+        * spoilt; keep a count of these so scrutineers can check that the 
numbers add up
+        * array( <round number> => <number>
+        */
+       var $spoilt = array( 1 => 0);
+       var $exhausted = array( 1 => 0 );
+
+       // Total number of counting rounds
+       var $rounds = 0;
+
+       // Total number of candidates who received any votes at all
+       var $numCandidates = 0;
+
+       /**
+        * The results of the counting process
+        * array( <option id> => array( <round number> => <votes> ) )
+        */
+       var $results = array();
+
+
+       /**
+        * @param  $context SecurePoll_Context
+        * @param  $electionTallier SecurePoll_ElectionTallier
+        * @param  $question SecurePoll_Question
+        */
+       function __construct( $context, $electionTallier, $question ) {
+               parent::__construct( $context, $electionTallier, $question );
+
+               foreach ( $question->getOptions() as $option ) {
+                       $this->results[$option->getId()] = array();
+               }
+       }
+
+       /**
+        * Add a voter's preferences to the ballot bin
+        *
+        * @param  $scores array of <option_id> => <preference>
+        * @return bool Whether the vote was parsed correctly
+        */
+       function addVote( $scores ) {
+
+               foreach ( $scores as $oid => $score ) {
+                       if ( !isset( $this->results[$oid] ) ) {
+                               wfDebug( __METHOD__.": unknown OID $oid\n" );
+                               return false;
+                       }
+                       // Score of zero = no preference
+                       if( $score == 0 ){
+                               unset( $scores[$oid] );
+                       }
+               }
+
+               $this->numCandidates = max( $this->numCandidates, count( 
$scores ) );
+
+               // Simple way to check for duplicate preferences: flip the 
array, and the
+               // preferences will become duplicate keys.
+               $rscores = array_flip( $scores );
+               if( count( $rscores ) < count( $scores ) ){
+                       wfDebug( __METHOD__.": vote has duplicate preferences, 
spoilt\n" );
+                       $this->spoilt[1] ++;
+                       return true;
+               } elseif ( count( $rscores ) == 0 ) {
+                       wfDebug( __METHOD__.": vote is empty\n" );
+                       $this->exhausted[1]++;
+                       return true;
+               }
+
+               // Sorting also avoids any problem with voters skipping 
preferences (1, 2, 4, etc)
+               ksort( $rscores );
+
+               // Slightly ugly way to get the first element of the array when 
that might not
+               // have index zero
+               $this->ballots[reset($rscores)][] = $rscores;
+
+               return true;
+       }
+
+       function finishTally(){
+               while ( $this->rounds++ < $this->numCandidates ){
+                       // Record the number of ballots in each bin
+                       foreach( $this->ballots as $oid => $bin ){
+                               $this->results[$oid][$this->rounds] = count( 
$bin );
+                       }
+
+                       // Carry over exhausted ballot count from previous round
+                       $this->exhausted[$this->rounds + 1] = 
$this->exhausted[$this->rounds];
+                       $this->spoilt[$this->rounds + 1] = 
$this->spoilt[$this->rounds];
+
+                       // Sort the ballot bins by the number of ballots they 
contain
+                       uasort( $this->ballots, array( __CLASS__, 
'sortByArraySize' ) );
+
+                       // The smallest bin is now at the end of the list; kill 
it
+                       $loser = array_pop( $this->ballots );
+
+                       // And redistribute its ballots to the other bins
+                       foreach( $loser as &$ballot ){
+                               $reused = false;
+                               foreach( $ballot as $pref => $oid ){
+                                       if( !array_key_exists( $oid, 
$this->ballots ) ){
+                                               unset( $ballot[$pref] );
+                                       } else {
+                                               $this->ballots[$oid][] = 
$ballot;
+                                               $reused = true;
+                                               break;
+                                       }
+                               }
+                               if( !$reused ){
+                                       $this->exhausted[$this->rounds + 1] ++;
+                               }
+                       }
+               }
+
+               // We marked every ballot as exhausted after the final round, 
which is a bit silly
+               array_pop( $this->exhausted );
+               array_pop( $this->spoilt );
+
+               // Sort the results so the winner is on top
+               uasort( $this->results, array( __CLASS__, 'sortByArraySize' ) );
+       }
+
+       public static function sortByArraySize( $a, $b ){
+               if( !is_array( $a ) || !is_array( $b ) || count( $a ) == count( 
$b ) ){
+                       return 0;
+               } else {
+                       return count( $a ) < count( $b );
+               }
+       }
+
+       function getHtmlResult() {
+               global $wgLang;
+
+               $s = "<table class=\"securepoll-results\">\n";
+
+               $lines = array();
+               foreach( $this->results as $oid => $data ){
+                       $option = $this->optionsById[$oid];
+                       $res = implode( $data, '</td><td>' );
+                       $name = $option->parseMessageInline( 'text' );
+                       $lines[] = "<tr><th>$name</th><td>$res</td></tr>";
+               }
+
+               $t = '<th></th>';
+               for( $i = 1; $i < $this->rounds; $i++ ){
+                       $ordinal = wfMsg( 'securepoll-round', 
$wgLang->romanNumeral( $i ) );
+                       $t .= "<th>$ordinal</th>";
+               }
+
+               $s .= "<tr>$t</tr>\n<tr>" . implode( $lines, "\n" ) . "</tr>\n";
+
+               $exhausted = wfMsg( 'securepoll-exhausted' );
+               $s .= "<tr class='securepoll-exhausted'><th>$exhausted</th>";
+               $s .= "<td>" . implode( array_values( $this->exhausted ), 
"</td><td>" ) . "</td></tr>\n";
+
+               $spoilt = wfMsg( 'securepoll-spoilt' );
+               $s .= "<tr class='securepoll-spoilt'><th>$spoilt</th>";
+               $s .= "<td>" . implode( array_values( $this->spoilt ), 
"</td><td>" ) . "</td></tr>\n";
+
+               $s .= "</table>\n";
+               return $s;
+       }
+
+       function getTextResult() {
+               // Calculate column width
+               $width = 10;
+               foreach ( $this->results as $oid => $rank ) {
+                       $option = $this->optionsById[$oid];
+                       $width = min( 57, max( $width, strlen( 
$option->getMessage( 'text' ) ) ) );
+               }
+
+               // Show the results
+               $qtext = $this->question->getMessage( 'text' );
+               $s = '';
+               if ( $qtext !== '' ) {
+                       $s .= wordwrap( $qtext ) . "\n";
+               }
+
+               foreach ( $this->results as $oid => $data ) {
+                       $option = $this->optionsById[$oid];
+                       $otext = $option->getMessage( 'text' );
+                       if ( strlen( $otext ) > $width ) {
+                               $otext = substr( $otext, 0, $width - 3 ) . 
'...';
+                       } else {
+                               $otext = str_pad( $otext, $width );
+                       }
+                       $s .= $otext . ' | ';
+                       $s .= implode( array_values( $data ), ' | ' ) . "\n";
+               }
+               return $s;
+       }
+}
+


Property changes on: 
trunk/extensions/SecurePoll/includes/talliers/AlternativeVoteTallier.php
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: trunk/extensions/SecurePoll/includes/talliers/Tallier.php
===================================================================
--- trunk/extensions/SecurePoll/includes/talliers/Tallier.php   2011-05-27 
09:34:16 UTC (rev 88961)
+++ trunk/extensions/SecurePoll/includes/talliers/Tallier.php   2011-05-27 
09:42:25 UTC (rev 88962)
@@ -22,6 +22,8 @@
                        return new SecurePoll_SchulzeTallier( $context, 
$electionTallier, $question );
                case 'histogram-range':
                        return new SecurePoll_HistogramRangeTallier( $context, 
$electionTallier, $question );
+               case 'alternative-vote':
+                       return new SecurePoll_AlternativeVoteTallier( $context, 
$electionTallier, $question );
                default:
                        throw new MWException( "Invalid tallier type: $type" );
                }

Modified: trunk/extensions/SecurePoll/resources/SecurePoll.css
===================================================================
--- trunk/extensions/SecurePoll/resources/SecurePoll.css        2011-05-27 
09:34:16 UTC (rev 88961)
+++ trunk/extensions/SecurePoll/resources/SecurePoll.css        2011-05-27 
09:42:25 UTC (rev 88962)
@@ -97,3 +97,17 @@
 .securepoll-election-closed {
        color: #aaa;
 }
+
+.securepoll-results th {
+       padding-right:5px;
+       text-align: left;
+}
+
+tr.securepoll-exhausted th, tr.securepoll-exhausted td {
+       font-style: italic;
+       border-top: 1px solid #aaa;
+}
+
+tr.securepoll-spoilt {
+       font-style: italic;
+}
\ No newline at end of file


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to