Skip to content

Commit

Permalink
Merge pull request #134 from julien-boudry/WORK/CivsFormat
Browse files Browse the repository at this point in the history
Converters: Support for exporting to Civs Format
  • Loading branch information
julien-boudry authored May 19, 2023
2 parents 8355956 + 43b3ab4 commit e725419
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 2 deletions.
141 changes: 141 additions & 0 deletions Tests/src/Tools/Converters/CivsFormatTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

declare(strict_types=1);

namespace CondorcetPHP\Condorcet\Tests;

use CondorcetPHP\Condorcet\Election;
use CondorcetPHP\Condorcet\Tools\Converters\CivsFormat;
use PHPUnit\Framework\TestCase;
use SplTempFileObject;

class CivsFormatTest extends TestCase
{
protected Election $election;

protected function setUp(): void
{
$this->election = new Election;
$this->election->parseCandidates('A;B;C');
}

public function testSimple(): void
{
$this->election->parseVotes('A>B>C * 2');
$this->election->parseVotes('C>B>A * 2');
$this->election->parseVotes('B=C>A * 2');

$r = CivsFormat::createFromElection($this->election);

self::assertSame(
<<<'CIVS'
# Candidates: A / B / C
1,2,3
1,2,3
3,2,1
3,2,1
2,1,1
2,1,1
CIVS
,
$r
);
}

public function testImplicit(): void
{
$this->election->parseVotes('A>B');
$this->election->parseVotes('C');
$this->election->parseVotes('A=B');

$r = CivsFormat::createFromElection($this->election);

self::assertSame(
<<<'CIVS'
# Candidates: A / B / C
1,2,3
2,2,1
1,1,2
CIVS
,
$r
);
}

public function testExplicit(): void
{
$this->election->setImplicitRanking(false);

$this->election->parseVotes('A>B');
$this->election->parseVotes('C');
$this->election->parseVotes('A=B');

$r = CivsFormat::createFromElection($this->election);

self::assertSame(
<<<'CIVS'
# Candidates: A / B / C
1,2,-
-,-,1
1,1,-
CIVS
,
$r
);
}

public function testWeight(): void
{
$this->election->parseVotes('A>B>C ^3');

// Deactivated
$r = CivsFormat::createFromElection($this->election);

self::assertSame(
<<<'CIVS'
# Candidates: A / B / C
1,2,3
CIVS
,
$r
);

//A ctivated
$this->election->allowsVoteWeight(true);

$r = CivsFormat::createFromElection($this->election);

self::assertSame(
<<<'CIVS'
# Candidates: A / B / C
1,2,3
1,2,3
1,2,3
CIVS
,
$r
);
}

public function testWriteToFile(): void
{
$file = new SplTempFileObject;

self::assertSame(0, $file->key());

$this->election->parseVotes('A>B; B>C');

CivsFormat::createFromElection(election: $this->election, file: $file);

$file->seek(0);
self::assertSame("# Candidates: A / B / C\n", $file->current());

$file->seek(1);
self::assertSame("1,2,3\n", $file->current());

$file->seek(2);
self::assertSame('3,1,2', $file->current());

self::assertTrue($file->eof());
}
}
5 changes: 3 additions & 2 deletions src/Console/Commands/ConvertCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use CondorcetPHP\Condorcet\Console\Helper\CommandInputHelper;
use Symfony\Component\Console\Input\{InputArgument, InputInterface, InputOption};
use CondorcetPHP\Condorcet\Throwable\Internal\CondorcetInternalException;
use CondorcetPHP\Condorcet\Tools\Converters\{CondorcetElectionFormat, DavidHillFormat, DebianFormat};
use CondorcetPHP\Condorcet\Tools\Converters\{CivsFormat, CondorcetElectionFormat, DavidHillFormat, DebianFormat};
use CondorcetPHP\Condorcet\Tools\Converters\Interface\{ConverterExport, ConverterImport};
use SplFileObject;
use Symfony\Component\Console\Attribute\AsCommand;
Expand All @@ -33,6 +33,7 @@ class ConvertCommand extends Command
CondorcetElectionFormat::class,
DebianFormat::class,
DavidHillFormat::class,
CivsFormat::class,
];

protected readonly string $fromConverter;
Expand Down Expand Up @@ -92,7 +93,7 @@ public function initialize(InputInterface $input, OutputInterface $output): void

$this->toConverter = match (true) {
$input->getOption('to-condorcet-election-format') => CondorcetElectionFormat::class,
// $input->getOption('to-civs-format') => CivsFormat::class,
$input->getOption('to-civs-format') => CivsFormat::class,

default => throw new CondorcetInternalException('The option defining the output format is missing')
};
Expand Down
85 changes: 85 additions & 0 deletions src/Tools/Converters/CivsFormat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/

declare(strict_types=1);

namespace CondorcetPHP\Condorcet\Tools\Converters;

use CondorcetPHP\Condorcet\Election;
use CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes\{Description, FunctionParameter, FunctionReturn, PublicAPI};
use CondorcetPHP\Condorcet\Tools\Converters\Interface\ConverterExport;

class CivsFormat implements ConverterExport
{
public const COMMAND_LINE_OPTION_NAME = 'civs-format';

////// # Static Export Method //////

#[PublicAPI]
#[Description("Create a CondorcetElectionFormat file from an Election object.\n")]
#[FunctionReturn("If the file is not provided, it's return a CondorcetElectionFormat as string, else returning null and working directly on the file object (necessary for very large non-aggregated elections, at the risk of memory saturation).")]
public static function createFromElection(
#[FunctionParameter('Election with data')]
Election $election,
#[FunctionParameter('If provided, the function will return null and the result will be writing directly to the file instead. _Note that the file cursor is not rewinding_')]
?\SplFileObject $file = null
): true|string {
$r = '';

$header = '# Candidates: '. implode(' / ', $election->getCandidatesListAsString());

if ($file) {
$file->fwrite($header);
} else {
$r .= $header;
}

$rankedModel = [];

foreach (array_keys($election->getCandidatesList()) as $candidateId) {
$rankedModel[$candidateId] = null;
}

foreach ($election->getVotesListGenerator() as $vote) {
$voteRanking = $vote->getContextualRanking($election);

$ranked = $rankedModel;

foreach ($voteRanking as $rank => $candidates) {
foreach ($candidates as $oneCandidate) {
$ranked[$election->getCandidateKey($oneCandidate)] = $rank;
}
}

ksort($ranked, \SORT_NUMERIC);

for ($weightDone = 0; $weightDone < $vote->getWeight($election); $weightDone++) {
$line = "\n";
$i = 0;

foreach ($ranked as $rank) {
$i++ > 0 && $line.= ',';
$line .= $rank ?? '-';
}

if ($file) {
$file->fwrite($line);
} else {
$r .= $line;
}

$line = '';
}


}

return ($file) ? true : $r;
}
}

0 comments on commit e725419

Please sign in to comment.