julien-boudry / Condorcet

Command line application and PHP library, providing an election engine with a high-level interface. Native support 20+ voting methods, easy to extend. Support simple elections with ease or billions of votes in low resource environment. Intensively tested and highly polyvalent.
https://www.condorcet.io
MIT License
119 stars 11 forks source link

getWinner & getName: various questions #42

Closed phplicengine closed 3 years ago

phplicengine commented 3 years ago
Q A
Type Bug / Support
Concorcet version 2.x
PHP version 7.4
Installation Method __CondorcetAutoload.php

Beside my another question regarding = in my another issue, I have another problem too:

if (isset($_POST) && $_POST != "") {

    arsort($_POST['item']);
    foreach ($_POST['item'] as $game => $priority) {
         $games[] = $game;
    }
    Condorcet::setDefaultMethod('Schulze'); // Argument: A supported method  

    $election = new Election ();

    $election->addCandidate(new Candidate ('Wingspan'));
    $election->addCandidate(new Candidate ('Scythe'));
    $election->addCandidate(new Candidate ('Spirit Island'));
    $election->addCandidate(new Candidate ('Everdell'));

    $vote1 = new Vote ( $games );
    $election->addVote($vote1);  

    $vote2 = new Vote ( 'Scythe > Spirit Island = Everdell = Wingspan' );
    $election->addVote($vote2);

   $result = $election->getResult();

   echo 'Schulze winner is : ' . $election->getWinner('Schulze')->getName() . '<br />';
}

If I have only one $vote, it works fine, but if I have both vote as above, I get this error: Fatal error: Uncaught Error: Call to a member function getName() on array

Isn't it a bug? or I did a mistake?

phplicengine commented 3 years ago

Now I believe there is bug with getName(), It happens when more than one candidate gets rank 1.

julien-boudry commented 3 years ago

Hi, See the documentation for getWinner() here : https://github.com/julien-boudry/Condorcet/blob/master/Documentation/Election%20Class/public%20Election--getWinner.md

If you use an advanced method instead of Natural, you can get an array with multiples winners.

Here, Schulze's method certainly produces a tie for first place, which may be normal, depending on the input votes. getWinner therefore returns an array.

phplicengine commented 3 years ago

please give a sample code how to use advance method? @julien-boudry

phplicengine commented 3 years ago

Do you mean this?

Election->getWinner('advanced')

?

julien-boudry commented 3 years ago

Election->getWinner() is always an advanced method. Default is 'Schulze Winning' method. Most of the advanced methods can produce a tie on rank, including top rank.

If you want the Natural Condorcet computation, the Marquis de Condorcet Winner. You must use Election->getCondorcetWinner() method. But be careful, the result can be NULL. The advanced methods almost always gives results, not the original Condorcet method. https://github.com/julien-boudry/Condorcet/blob/master/Documentation/Election%20Class/public%20Election--getCondorcetWinner.md

phplicengine commented 3 years ago

Here is my full source. If in form you choose the same number for more than one item, it gets error for getName() on getWinner(). What wrong I did as it is Schulze as you said.

use CondorcetPHP\Condorcet\Condorcet;
use CondorcetPHP\Condorcet\Election;
use CondorcetPHP\Condorcet\Candidate;
use CondorcetPHP\Condorcet\CondorcetUtil;
use CondorcetPHP\Condorcet\Vote;

if (isset($_POST) && $_POST != "") {

Condorcet::setDefaultMethod('Schulze');
$election = new Election ();

print("<pre>");
print_r($_POST['item']);

// To get Candidates by Category via Doctrine orm
$category = $em->getRepository('Entities\Category')->findOneBy(['id' => 1]);
$candidates = $category->getCandidates();

$total_items = 0;
foreach ($candidates as $candidate) {
         $candidate = $candidate->getCandidate();
         $election->addCandidate(new Candidate($candidate));
         ++$total_items;
}

$votes = array();
foreach ($_POST['item'] as $item => $ranking) {
    $vote_factor = 1 + $total_items - $ranking;
    if (isset($votes[$vote_factor])) {
       $votes[$vote_factor] = (array) $votes[$vote_factor];
       $votes[$vote_factor][] = $item;
    } else {
       $votes[$vote_factor] = $item;
    }
}    

$result = $election->addVote(new Vote($votes));  

print("<br />");
foreach ($election->getResult('Schulze') as $rank => $candidates) :
    echo 'Rank ' . $rank . ': ';
    echo implode(', ', $candidates);
    echo '<br />';
endforeach;

print_r($result->getSimpleRanking()); // To be saved in db.
echo 'Schulze winner is : ' . $election->getWinner('Schulze')->getName() . '<br />';
}

?>

<html>
    <body>
        <form method="post">
        Wingspan: <select name="item[Wingspan]" id="Wingspan">
                         <option value="1">1</option>
                         <option value="2">2</option>
                         <option value="3">3</option>
                         <option value="4">4</option>
                 </select><br />
        Scythe:  <select name="item[Scythe]" id="Scythe">
                         <option value="1">1</option>
                         <option value="2">2</option>
                         <option value="3">3</option>
                         <option value="4">4</option>
                 </select><br />
        Spirit Island: <select name="item[Spirit Island]" id="Spirit Island">
                         <option value="1">1</option>
                         <option value="2">2</option>
                         <option value="3">3</option>
                         <option value="4">4</option>
                  </select><br />
        Everdell: <select name="item[Everdell]" id="Everdell">
                         <option value="1">1</option>
                         <option value="2">2</option>
                         <option value="3">3</option>
                         <option value="4">4</option>
                  </select><br />
        <input type="submit" name="submit" value="submit">
        </form>
    </body>
</html>
phplicengine commented 3 years ago

I think you meant:

foreach ($election->getWinner('Schulze') as $winner) {
    print($winner->getName()."<br />");
}

Is it correct? But it gives nothing if there is only one candidate. How do I know if there are only one or more than one winner(s) to fix this problem?

Please see my source, it seems it is working well, just for sure I want you to look at the source to see if everything is correct or any improvements you can suggest?

It seems

if (isset($_POST) && $_POST != "") {

is not working and for first run when there is nothing posted yet, I get these notices:

Notice:  Undefined index: item on line 19

Notice:  Undefined index: item on line 34

Warning:  Invalid argument supplied for foreach() on line 34.

Why I am getting these errors? And is there any way to combine both foreach() as one and how to generate form items field from items fetched from db rather than hardcoding them?

I know these are not directly related to Condorcet, but I highly appreciate your help.

julien-boudry commented 3 years ago

Hi @phplicengine,

Two ways.

1.

$winners = $election->getWinner('Schulze');

if (is_array($winners) {
  foreach ($winners as $oneWinner) {
      echo($oneWinner->getName()."<br />");
  }
}
else {
    echo($winners->getName());
}

2.

// On getResult, the ranks are always arrays.
  foreach ($election->getResult('Schulze')[1] as $oneWinner) {
      echo($oneWinner->getName()."<br />");
  }
julien-boudry commented 3 years ago

About your problem with the $_POST in your code. I suppose that $_POST is always set (so your isset is useless) and is never an empty string. So, your condition is always true.

Look with empty()or count() or isset($_POST['item'])

phplicengine commented 3 years ago

Here is my code I am using: https://github.com/julien-boudry/Condorcet/issues/42#issuecomment-831346042 I want to add a blank select option, that my users if they don't want to have some candidates in voting just use the blank select option and don't be forced to select a ranking number from the drop down menu. How to omit blank options completely from listing and voting? @julien-boudry