Third Script: Finished Version

colomon on 2008-12-27T02:15:01

This is the instant runoff election task. I don't feel like I had terrible problems writing it, but I feel like it's a very satisfying piece of code, either.

my $votes_file = "votes.txt";

sub EffectiveVote (Str $vote, Hash %skip) { my @vote = split ',', $vote; for @vote -> $choice { if (! %skip{$choice}.defined) { return $choice; } } die "No valid vote?!"; }

sub CountRound (Array @votes, Hash %skip) { say "{@votes.elems} votes"; my %count; my $total = 0; for @votes -> $vote { my $choice = EffectiveVote($vote, %skip); %count{$choice}++; $total++; } my %percentages; for %count.keys -> $choice { %percentages{$choice} = %count{$choice} / $total; } return %percentages; }

my @votes;

my $votes = open($votes_file); for (=$votes) -> $vote { push @votes, $vote; }

my $count = 0; my %skip; while (1) { say; say "Round {++$count}"; my %percentages = CountRound(@votes, %skip); my @ordered = sort { %percentages{$^b} <=> %percentages{$^a} }, %percentages.keys; for @ordered -> $vote { say "$vote: {%percentages{$vote}}"; } if (%percentages{@ordered[0]} > 0.5) { say; say "The winner is {@ordered[0]} with {%percentages{@ordered[0]} * 100.0}% of the vote."; exit; }

my $skip = @ordered.pop; %skip{$skip} = 1; say "Skipping $skip"; }


Two difficulties of note here. First, I initially tried making @votes an array of arrays. I couldn't figure out any obvious way to make this work in Perl 6.

Second problem is
perl6(28102) malloc: *** error for object 0x2eb5a10: double free
*** set a breakpoint in malloc_error_break to debug
Segmentation fault
after the script properly finishes.


EffectiveVote

Aristotle on 2008-12-27T06:58:12

sub EffectiveVote ( Str $vote, Hash %skip ) {
    return $vote.split( ',' ).first( { ! %skip{$_}.defined } );
    die "No valid vote?!";
}

Re:EffectiveVote

colomon on 2008-12-27T10:05:37

Okay, that's definitely a big improvement over mine, and quite elegant. However, I'm wondering about return versus die here. (And my wondering is more complicated because I cannot find documentation for .first anywhere.)

If no valid vote is found, won't the first line return undefined rather than not return?

Re:EffectiveVote

Aristotle on 2008-12-27T10:52:54

Argh, yes. Good point.

Re:EffectiveVote

colomon on 2008-12-27T13:29:00

Would

sub EffectiveVote ( Str $vote, Hash %skip ) {
    return $vote.split( ',' ).first( { ! %skip{$_}.defined } )
        err die "No valid vote?!";
}

do it, in theory? (In practice, I haven't gotten err die to work for me yet.)

Re:EffectiveVote

colomon on 2008-12-27T13:33:48

Hey,

sub EffectiveVote ( Str $vote, Hash %skip ) {
    return $vote.split( ',' ).first( { ! %skip{$_}.defined } )
        // die "No valid vote?!";
}

actually compiled and it works! (I tested created a second votes file to make sure that it properly caught the condition, and it does.)

CountRound

Aristotle on 2008-12-27T07:10:06

sub CountRound ( @votes, %skip ) {
    my %fraction;
    my $f = { EffectiveVote( $^vote, %skip ) };
    for @votes.map( $f ) { %fraction{$_}++ }
    for %fraction.values { $_ /= @votes.elems }
    return %fraction;
}

Re:CountRound

colomon on 2008-12-27T13:42:00

That's an interesting approach. On consideration, I decided it would be easier to drop the percentage thing altogether, yielding this, which is slightly wordier than yours but dead simple.

sub CountRound (Array @votes, Hash %skip)
{
    my %count;
    for @votes -> $vote
    {
        %count{EffectiveVote($vote, %skip)}++;
    }
    return %count;
}

main program

Aristotle on 2008-12-27T07:27:53

Unlike the others this is untested.

my @votes = do {
    my $votes = open($votes_file);
    =$votes
};

my $count = 0;
my %skip;

loop {
    say "\nRound {++$count}";

    my @ranking = CountRound( @votes, %skip ).pairs.sort({.value});

    say sprintf "%s: %s", .key, .value
        for @ranking;

    given @ranking[0].value {
        when $_ > 0.5 {
            say sprintf "\nThe winner is %s with %.0f%% of the vote.", .key, .value;
            exit;
        }
    }

    my $skip = @ranking[-1].key;
    %skip{ $skip } = 1;
    say "Skipping $skip";
}

Re:main program

colomon on 2008-12-27T14:00:34

That CountRound line is nice. I've worked it into my code, I'll post another full version as a fresh post. Thanks!