I was considering sharing the Perl script I had written, but didn't want to clutter up my post about the winners. Someone asked for it in a comment on that post, so I'm sharing it.
I generally write code with lots of comments, so that a year later when the "maintenance coder" (which might be me!) comes to fix something, there'll be a mostly-accurate guide. Now that I've got these concussions, verbose comments are that much more useful -- because now merely "a day later" I've forgotten a lot of what I was doing.
Also, I generally split code into sections, a habit I started way back working in C, and have dragged it forward to Perl, Python, etc. And, another throwback to C, my scripting code generally just has a line like "exit main();" and then all processing happens inside the main() function/subroutine, and it calls out to others. I've also written my own logging module which I have reproduced several times (since the previous one was locked up in the previous job, and I don't steal code like some people steal pets[1]). (Logging module isn't included here, or used -- the script is self-contained.)
Thus, I won't annotate the code any further; I'll just post it below. Oh also, I'll note that I did this in an Ubuntu 16.04.3 VM, running on this Windows 10 laptop. When I went to copy/paste, it "helpfully" added a CR (carriage return; or, an extra blank line) to every line ending. Rather than going through the below and hitting "down, del" 178 times until each line ending was corrected, I just did an "svn up" on the laptop, as I had checked the code in to my personal Subversion repository. Subversion handles the line endings of text files correctly, so once it appeared on the laptop I was able to easily copy/paste, with no line-ending difficulty.
Also, wanted to note that there are many "print" statements throughout the code, so that as I was developing the solution I could see whether things were happening correctly. I left those in, so the actual output of the votees and number of votes, appears after that -- with a line of text indicating the above are the top 50, and the below are the rest.
I use ActiveState's Komodo IDE to edit and run the script while developing it. Excellent Integrated Development Environment! It's commercial, but they also have a free version Komodo Edit, which just edits, doesn't run the script (and I haven't used it, as I bought a personal license for the IDE years ago and have been upgrading every few years -- still on version 9 right now; I think, like Spinal Tap, they've turned it up to 11 :) ).
If you have any questions on anything in the below, post a comment and I'll do my best to clear up any confusion!
#!/usr/bin/perl
=head1 tabulate_votes2.pl
Second script to tabulate votes, to help me with Steemit. This is for the contest: https://steemit.com/life/@libertyteeth/libertyteeth-community-minnow-support-vote-for-up-to-five-of-your-favorite-minnows-50-will-get-a-5-slot-in-my-steemvoter
1. I do a voting contest, people nominate others, and the contest ends.
2. Gather the votes in a text document, with the voter on the first line, then their votes separated by spaces, commas, and "extra text", finally a blank line.
3. Run this script on it.
From my work log, a full description which I'll massage:
---
The script will need several aspects to it:
1. Detect self-votes and eliminate all votes from that account. Also keep a list of these, so it can present it.
2. Verify that the voter, and each voted account, is a valid account and alert me so I can try various changes to see if I can find the account that was intended.
3. If a voter has more than five votes, then replace the earlier ones with the later ones, so there is a maximum of five votes from any voter.
4. Then sort based on number of votes (likely I'll use Perl, and a hash, with the key being the account name, and the value being an integer, starting at 0 and
increment for each vote; then sort on the values in descending order and choose the top 50).
5. Verify each voted account qualifies as a minnow, and set aside any account that's "close" like between 100% and 110%, but no more.
6. Then spit out the list of the top 50, and those who were "close", and I'll finish manually. Also, those who self-voted, because it was the first freaking rule! :)
By "finish manually" I mean that there might have been an account which, when voted on, qualified. I might be able to determine that via steemd.com; or, perhaps, using
steemworld.com or steemnow.com. Alternately, I might just say "congratulations on escaping minnow status during this contest!" We'll see.
---
This script will produce the following output, separated into sections, with one line for each unique account within each section:
Actual votes:
account,number of votes
"Close" accounts:
account,number of votes
Self-voters:
account
Actually, this script now ONLY produces the "actual votes" -- this round, there weren't any self-voters, so don't need to account for that; and, to get the "close"
accounts, I need Python in order to access the STEEM blockchain.
=cut
=head2 ALWAYS USE STRICT AND WARNINGS
=cut
use strict;
use warnings;
=head2 MODULES USED FROM CPAN
=cut
use Getopt::Long;
=head2 MODULES WRITTEN IN-HOUSE
=cut
=head2 VARIABLE DECLARATIONS
=cut
my $vote_file; # file with votes, from the post -- format described above
=head2 SUBROUTINE DECLARATIONS
=cut
sub main();
=head2 MAIN FUNCTION
=cut
exit main();
=head2 SUBROUTINE DEFINITIONS
=cut
=head2 main
Main routine, which tabulates the votes and writes output.
=cut
sub main() {
GetOptions( "vote_file=s" => \$vote_file,
) or die "Error in command line arguments.\n";
die "ERROR: no vote_file specified!\n" if !defined $vote_file;
open FH, "<$vote_file" or die "ERROR: can't open file [$vote_file]!\n";
my @lines = ;
close FH;
my (%voters, %votes, %close, %self);
# The %voters hash: key will be the voter; value will be an array with up to five elements; remove first if adding sixth.
# %votes: key=votee, value=number of votes.
# %close: key=votee, value=number of votes. NOTE: NOT IMPLEMENTING, as I need Python to talk to the STEEM blockchain.
# %self: key=voter, value=array with up to five elements; remove first if adding sixth (these are who they WOULD have voted for, if one of them wasn't a self-vote).
my $voter;
LINE: foreach my $line ( @lines ) {
chomp $line; # remove trailing '\n'
if ( $line eq "" ) {
if ( !defined $voter ) {
# Could be two blank lines between voters' blocks; or, could be a blank line at the beginning of the file.
print "Skipping blank line\n";
next;
}
print "END current voter [$voter]\n\n";
$voter = undef; # saw a blank line while processing; so, we're done processing the current voter.
next;
}
# Okay, we checked for a blank line. Next, what to do when the line's not blank. First, if we're NOT processing a voter,
# then this line is the voter's name, so set it to start processing.
if ( !defined $voter ) {
$voter = $line;
print "BEGIN processing voter [$voter]\n";
next;
}
# If we're here, the line was not empty, and we are processing a voter's votes. So, extract any "@word" from this text, and add them as votees for that voter.
my @votees = split /@/, $line;
shift @votees; # remove the first element, which is "everything before the first '@'"
foreach my $votee ( @votees ) {
$votee =~ s/ .*$//; # remove any space to EOL
$votee =~ s/,$//; # remove any comma at the end of the votee's name
$votee =~ s/\.$//; # remove any period at the end of the votee's name
print " voted for [$votee]\n";
if ( $voter eq $votee ) {
print " SELF-VOTE DETECTED! All votes by [$voter] will be ignored.\n";
delete $voters{$voter};
$self{$voter} = 1;
next LINE; # Stop processing this voter's votes, they voted for themselves.
}
if ( grep( /^$votee$/, @{$voters{$voter}} ) ) {
print " NOTE: already voted for [$votee] so not adding to the array.\n";
next;
}
push @{$voters{$voter}}, $votee;
# At this point, if the voter has voted for more than five votees, remove one (from the front) so that there are only five.
if ( scalar @{$voters{$voter}} > 5 ) {
my $v = shift @{$voters{$voter}};
print " MORE THAN FIVE VOTES, so dropping [$v]\n";
}
}
# FYI, there weren't any self-votes, so that's good! Looks like it processes the vote file (votes.txt) just fine.
# Next step: output the top 50 that were voted for, as well as the rest -- I will look up whether they are "minnow status"
# either manually, or with a Python script -- as it requires Python in order to interact with the STEEM blockchain.
}
# Okay, now we have the %voters hash completely filled out. So, let's process it into the %votes hash.
foreach my $k ( sort keys %voters ) {
foreach my $v ( sort @{$voters{$k}} ) {
$votes{$v}++;
}
}
# Now, do the reverse: make a hash where the key is the number of votes, and the value is an array containing those voted for.
my %votes_by_num;
foreach my $k ( sort keys %votes ) {
push @{$votes_by_num{$votes{$k}}}, $k;
}
# Now we can output it. Walk through %votes_by_num, then walk through the array that is each key's value, printing out the account and
# the number of votes it obtained, as well as a leading count. If the count reaches 50, say "above are the winners".
my $i = 1; # counter, so can print another header once it hits 50
print "Winners:\n";
foreach my $k ( reverse sort { $a $b } keys %votes_by_num ) { # The "{ $a <=> $b }" does a numerical sort; default is alphabetical, which put "51" after "5"...
foreach my $a ( @{$votes_by_num{$k}} ) {
print "$i: $k $a\n";
$i++;
if ( $i eq 51 ) {
print "\n\nBELOW ARE THE REST OF THE VOTING RESULTS:\n";
}
}
}
print "breakpoint here!\n";
exit 0;
}
Enjoy!
[1] -- I haven't followed her TV career, but a very good friend many years ago shared the "Fuck Me, Ray Bradbury" video of hers (also hilarious!), and I looked for others back then and found this one which is ... odd. :)
(And, has a sad explanation of the behavior, towards the end.)


