Monday, October 3
Shadow

stv.php

<?php
/* Find files */
$files = glob( $argv[1] );
sort( $files );
echo "Reading from: \n- ", implode( "\n- ", $files ), "\n\n";

/* Read Amount of Seats from Command Line */
array_shift( $argv );
array_shift( $argv );
$seats = (int) $argv[0];

/* Read Candidates from Command Line */
array_shift( $argv );
$candidates = $argv;
echo "Candidates (in order of ballot):\n", implode( '  —  ', $candidates ), "\n\n";

/* Read votes */
foreach ( $files as $pref => $file )
{
	$data = unserialize( file_get_contents( $file ) );

	foreach ( $data as $voter => $choices )
	{
		$votes[$voter][$pref] = $candidates[$choices['choices'][0]];
	}
}

/** Options **/
$rejectDuplicatePreferencedVotes = false; // if true, drop the whole vote, otherwise collapse
/*************/

/* Reindex choices in each vote to get rid of original indexes, in case
 * voters picked not all preferences in order */
foreach ( $votes as $voteIdx => $preferences )
{
	$votes[$voteIdx] = array_values( $preferences );

	if ( sizeof( array_count_values( $preferences ) ) != sizeof( $preferences ))
	{
		if ( $rejectDuplicatePreferencedVotes )
		{
			printf(
				"Voter '%s' has duplicates (%s), rejecting\n",
				$voteIdx,
				implode( '  —  ', $preferences )
			);
			unset( $votes[$voteIdx] );
		} else {
			printf(
				"Voter '%s' has duplicates (%s), de-duplicating\n",
				$voteIdx,
				implode( '  —  ', $preferences )
			);
			$deDuplicated = [];

			foreach( $preferences as $pref )
			{
				$deDuplicated[$pref] = 1;
			}

			$preferences = array_keys( $deDuplicated );
			printf(
				"%s %s\n\n",
				str_repeat( ' ', strlen( $voteIdx ) + 24 ),
				implode( '  —  ', $preferences )
			);
			$votes[$voteIdx] = $preferences;
		}
	}
}
echo "\n";

/* Anonymise votes */
$votes = array_values( $votes );

class Stv
{
	private array $elected = [];
	private int   $quorum;
	private int   $currentRound = 1;
	private array $tally;
	private array $firstPreferences;
	private array $eliminated = [];

	function __construct( private array $candidates, private int $seats, private array $votes )
	{
		$this->showVotes();
		$this->prepareTally();
	}

	public function elect() : array
	{
		$this->calculateQuorum();

		do {
			$this->doRound();
		} while ( count( $this->elected ) < $this->seats );

		return $this->elected;
	}

	private function calculateQuorum()
	{
		$cV = count( $this->votes );
		$cC = count( $this->candidates );
		$s  = $this->seats;
		$this->quorum = floor( $cV / ( $s + 1 ) ) + 1;

		echo "Votes:      {$cV}\n";
		echo "Candidates: {$cC}\n";
		echo "Seats:      {$s}\n";
		echo "Quorum:     {$this->quorum}\n";
		echo "\n";
	}

	private function prepareTally()
	{
		foreach ( $this->candidates as $candidate )
		{
			$this->tally[$candidate] = [];
		}

Leave a Reply