Stories
Slash Boxes
Comments

Dev.SN ♥ developers

The Fine print: The following are owned by whoever posted them. We are not responsible for them in any way.

Journal by prospectacle

I've updated my voting system to allow preferential voting (the previous method uses approval voting). It gives a score to each candidate based on its rank. E.g. if there are 10 candidates, and you give a candidate 1st preference, it gets 10 points, second preference gets 9 points, etc.

It's therefore functionally equivalent to "range voting".

<?php

/* How to use:
    Put all emails in an array with values in $emails["text"] and $emails["user_id"];
    Put the list of valid candidates in the function valid_candidate();
    Put the check for user-authorisation in function valid_user();
    Enjoy.
*/

$emails_array = array(
    array("user_id"=>234, "text"=>"
            option1 = 1
            option3 = 2
            optionwhatever = 3
        "),
    // Duplicate user, will be handled correctly.
    array("user_id"=>234, "text"=>"
        Oops forgot one I like:
        Optionfour = 2
        // Did I mention:
        option1=1
        "
        ),
    array("user_id"=>1234,
        "text"=>"
        // I hate option1
        Option1 = 6
        option2 = 1
        ")
    );

function valid_user($user_id){return true;} // put user filter in here if necessary
function valid_candidate($name){ return true;} // is the name one of the candidates?

$number_of_options = 6;

// Process all emails
foreach ($emails_array as $email)
{

  // Is it a valid registered user?
  if (valid_user($email["user_id"]))
  {

    // Process each line of the email
    $email_lines = explode("\n", trim($email["text"]));
    foreach ($email_lines as $this_line)
    {
      // Does it have an '=' sign and only one = sign
      $equals_sign = strpos($this_line, "=");
      if ($equals_sign !== false)
      {
        $cleaned_up_line_text = trim($this_line, ";.!\t\n\r\0");
        $parts_of_line = explode("=", $cleaned_up_line_text);
        if (count($parts_of_line) == 2)
        {
            // Is it a valid candidate and rank?
            // Candidate is in lower case.
            $candidate = strtolower(trim($parts_of_line[0]));
            $rank = intval(trim($parts_of_line[1]));
            if (valid_candidate($candidate) && ($rank > 0) && ($rank <= $number_of_options))
            {
                // Get the score for this candidate.
                // The score is the number of options - how far it is ranked below 1.
                // e.g. a rank of 1 would give it a score of $number_of_options.
                // a rank of 2 gives it a score of $number_of_options -1.
                // See "range voting".
                $score = $number_of_options - ($rank-1);

                // Make sure this vote for this user hasn't already been cast
                if (!isset($user_votes[$email["user_id"]]) ||
                    !isset($user_votes[$email["user_id"]][$candidate]))
                {
                    // Remember this user has voted for this name already.
                    $user_votes[$email["user_id"]][$candidate] = true;

                    // Count the vote towards the total
                    if (!isset($candidate_votes[$candidate]))
                        $candidate_votes[$candidate]=$score;
                    else $candidate_votes[$candidate]+= $score;
                }
            } // end of check for valid vote values.
        } // of check for correctly formatted vote
      } // of check for equals sign
    } // End of for loop for lines of email
  } // of check for valid user.
} // end of for loop for all emails.

print "votes:<br>";
print_r($user_votes);
print "<br><Br>";
print "candiate_votes<Br>";
print_r($candidate_votes);

?>

Display Options Breakthrough Reply to Article Mark All as Read Mark All as Unread
The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
  • (Score: 0) by crutchy on Saturday March 22 2014, @10:50PM

    by crutchy (179) on Saturday March 22 2014, @10:50PM (#19866) Homepage Journal

    any reason why you prefer print to echo?

    • (Score: 2) by prospectacle on Sunday March 23 2014, @01:30AM

      by prospectacle (3422) on Sunday March 23 2014, @01:30AM (#19890) Journal

      Not a good reason, no. I favoured "print" early on, and it became a habit.

      Never got too acquainted with their differences, to be honest, beyond satisfying myself that "print" would do everything I wanted.

      Should I reconsider?

      --
      If a plan isn't flexible it isn't realistic
    • (Score: 1) by michealpwalls on Friday March 28 2014, @09:10AM

      by michealpwalls (3920) on Friday March 28 2014, @09:10AM (#22486) Homepage

      My reply is a prime example of Parkinson's Law of Triviality [wikipedia.org], but I really could not resist myself :P

      The consensus seems to be that echo is up to 20% faster than print. I don't claim to know why this is, but I would assume it is because print not only returns a value (Whether or not you use it) but it is also part of the precedence table, allowing it to be used in complex expressions.

      At the end of the day, I think we're talking about 20% of nanoseconds.. Completely insignificant, but interesting to talk about :)

  • (Score: 2) by martyb on Thursday March 27 2014, @11:06AM

    by martyb (76) on Thursday March 27 2014, @11:06AM (#22078) Journal

    First off, let me say: "Nicely done!" Although I know your intention was to support voting via e-mail, I suspect that the code could be re-purposed to process results from an updated poll-booth source. Maybe replace the term "e-mail" with "ballot"?

    It's been quite a while since I coded perl, so please bear with me.

    It seems to me that a user could vote multiple choices at the same preference level...

    Case 0:
    1 foo
    2 bar
    3 bazz

    Case 1:
    3 foo
    1 bar
    2 bazz

    Case 2:
    1 foo
    2 bar
    2 bazz

    Case 3:
    1 foo
    3 bar
    3 bazz

    Case 4:
    1 foo
    1 bar
    1 bazz

    Case 5:
    3 foo
    3 bar
    3 bazz

    Case 6:
    3 foo
    7 bar
    7 bazz
    7 buzz
    7 fizz
    7 fizz-buzz
    7 bizz-fuzz

    Cases (0) and (1) are what I'd consider "normal" cases.

    Case (2) suggests a clear preference for one candidate, and an ambivalence about two others. Okay, I can understand that.

    Case (3) is just like case (2), but the ambivalent choice is lower-weighted... Hrmmm!? I can, kind of, understand that.

    Case (4) is just as ambivalent as case (5), but if I read things correctly, case (4) gives every candidate max points, but case (5) gives no points to any candidate?

    Case (6) is, admittedly, pathological. But, do we process it? Give an error?

    Questions:

    1. Are all these cases considered valid inputs?
    2. If not, how do we communicate to the votes that their ballot is "spoiled".
    3. Is my assessment of the processing correct?
    4. Is that the intended processing for those cases?
    5. Will there be positive confirmation that the ballot was received and is deemed valid?

    And, of course, I'm assuming there are no write-in candidates. Right?

    So, in general, it looks really good!

    • (Score: 2) by prospectacle on Sunday March 30 2014, @03:06AM

      by prospectacle (3422) on Sunday March 30 2014, @03:06AM (#23080) Journal

      Good questions. There seem to be three main issues raised:

      a - What about duplicate rankings?
      b - What about strange/invalid votes?
      c - How do we give a confirmation/rejection?

      -----

      a) Duplicate rankings are accepted by this script.

      Giving two candidates the same rank awards them the same number of points. So it's only useful to do this if you also give a third candidate a different ranking. In other words giving multiple candidates the same rank is meaningful if and only if you don't give every single candidate the same rank. I'll write another version that doesn't allow duplicate ranks and put it in my journal. Then one can choose whatever version they prefer.

      In your cases (4) and (5), (assuming there are only three candidates) these two cases would be identical in effect. Imagine all votes had been counted but yours, and you then gave an equal ranking to every candidate. This means every candidate would go up by the same amount of points, as a result of your vote, and so if two candidates were 1 point apart before your vote, they would be 1 point apart after your vote.

      In your cases (2) and (3), you've voted for every candidate, but you've effectively given "foo" an advantage over the others, and in case (3), a bigger advantage than in case (2).

      So if "foo" was one point behind "bar", then voting as shown in case (2) would lead to these two candidates being deadlocked, but voting as in case (3) would lead to "foo" taking the lead by one point.

      -----

      b) Crazy votes are filtered out.

      They're treated the same as comments that might be included in the voter email or comment. The checks that are done for each line of text are:
      - Is this person allowed vote?
      - Is this line of their comment/text/email formatted as a vote?
      - Is the specified candidate a valid one?
      - Is the ranking a valid number?
      - Have they already voted for this candidate?

      If it passes all of those tests (the first four must be "yes" the last one must be "no"), then it counts the vote, otherwise, not. So for your case (6), (again assuming there are only three candidates) their vote for "foo" would count, and the other ranks (all 7) wouldn't count. It would be the same as only voting for "foo".

      This also means, as you pointed out, that write-in candidates are not allowed, but it would be a simple change to allow them, when necessary. You'd just get rid of the third check listed above, and set a maximum rank each person can assign (at present the maximum rank is based on the number of candidates).

      -----

      c) How to confirm. Good question.

      You could have a preview/verify screen that run all of the checks/validation and none of the counting. It would say "If you submit this vote, this is how it will count" and have the preview formatted in a specific, uniform way. Invalid votes (lines of text that did not cast a valid vote) could be highlighted and displayed separately, so users can tell what's valid and what isn't.

      This could mostly be handled by that same code that's used to count the votes, since it already does all the required checks for each line of voter's submission (see answer (b)).

      Anyway I hope this answers your questions. Let me know if it doesn't or if you have others. As I said I'll make a version that doesn't allow duplicate ranks, just in case it's of interest to anybody. I'll also add the option to allow or disallow write-in candidates.

      --
      If a plan isn't flexible it isn't realistic
      • (Score: 2) by martyb on Monday March 31 2014, @08:07AM

        by martyb (76) on Monday March 31 2014, @08:07AM (#23514) Journal

        Interesting discussion; thanks for the reply!

        Based on this and your prior posts, I'm starting to notice a pattern. It seems to me that there would be value in having separate functions for:

        1. Ballot extraction/filtering - A function that, given an arbitrary chunk of text, extracts and returns only that part which contains the vote.

        2. Ballot validation - A function that, given a filtered ballot and a set of flags, tests to see if the ballot is valid for the selected type of vote. Could be implemented as sub-functions selected by the passed flags. Flags could be OR'ed together and passed as a single argument. (I am not up-to-date on the different kinds of votes that could be taken, so please consider this as very rough) Flags might include:

          1. BALLOT_RANKING_DUPE_ALLOWED - when processing rank voting, multiple votes with the same rank are permitted
          2. BALLOT_RANKING_GAP_ALLOWED - when processing rank voting, votes need not be consecutive numbers
          3. BALLOT_RANKING_WRITEIN_ALLOWED - when processing rank voting, the voter may write-in one (or more) candidate(s).

          May also want to have an argument (e.g. ballot_choice_limit) to limit the number of votes on a ballot that will be tabulated. I.e. pick the top 3 (three) candidates out of the list of 7 (seven) candidates offered.

          The function would return a string containing: count of the errors found followed by the error text for each, separated by new-line characters. If there are no errors found, then the returned string would contain a zero, new-line char, and then a new-line delimited string containing the validated votes.

        3. Ballot vote extraction and scoring. Given that the vote has been validated, apply whatever scoring rules are desired. If there were 5 items on the ballot and the user rank voted them from 1 to 5, this might provide scoring (from top to bottom) of "5,4,3,2,1" or "10,8,6,4,2" or "25,16,9,4,1"... you get the idea.

        4. Vote tallying - This would be a separate pass through the validated and scored votes. Simply a bookkeeping step to find the winner.

        Am out of time, so will post this now. Am interested to see your feedback!

        • (Score: 2) by prospectacle on Monday March 31 2014, @10:37PM

          by prospectacle (3422) on Monday March 31 2014, @10:37PM (#23851) Journal

          Yes, it's definitely getting to the point where the feature, line, and configuration-option count suggests a good refactor is in order. The separation of function you describe seems sensible. Additional options might be to parse either plain text, or a form submission with checkboxes, or drop-downs. I could also add an option for whether to use ranked or approval voting.

          Probably I won't do it any time soon though, since I don't imagine anyone is or will be using this code. Still it's been a fun little project and maybe I'll find more time for it before long.

          Thanks for your feedback and suggestions.

          --
          If a plan isn't flexible it isn't realistic
          • (Score: 2) by martyb on Tuesday April 01 2014, @09:41PM

            by martyb (76) on Tuesday April 01 2014, @09:41PM (#24557) Journal

            Yes, it's definitely getting to the point where the feature, line, and configuration-option count suggests a good refactor is in order. The separation of function you describe seems sensible. Additional options might be to parse either plain text, or a form submission with checkboxes, or drop-downs. I could also add an option for whether to use ranked or approval voting.

            A simple text-entry field would suffice for starters. Use a "validate" button to invoke the ballot validation checking. Use a "Reset" button to re-load with an initial, clean ballot. Repeat display, parsing, and validation until it comes back "clean". Then a "Vote" button could display and when clicked, would save this new-line delimited vote string to the DB.

            Probably I won't do it any time soon though, since I don't imagine anyone is or will be using this code. Still it's been a fun little project and maybe I'll find more time for it before long.

            We are looking at a way to roll out community voting. For example, for the name of the site. I think you've got 80-90% of what we need already coded. I'd encourage you to continue on this endeavor... you're sooooo close!

            Thanks for your feedback and suggestions.

            It's been a pleasure! I hope you decide to continue; we really could use this, and soon!